Nginx 使用简明指南

欢迎你来读这篇博客,这篇博客主要记录 Nginx 在实际 Web 项目中的常见使用方式,包括安装、核心配置、静态资源托管、反向代理、负载均衡、HTTPS、HTTP 自动跳转 HTTPS,以及 Docker、Kubernetes 两种部署场景。

如果只把 Nginx 理解成“放静态文件的 Web Server”,会低估它的价值。在实际项目中,它更常出现在系统入口层:浏览器先访问 Nginx,由 Nginx 负责 TLS/HTTPS、域名、静态资源、反向代理和流量转发,再把动态请求交给 Spring Boot、Tomcat、Node.js 等后端服务。

序言

什么是 Nginx

Nginx 是一个高性能的 HTTP Server、反向代理服务器和通用 TCP/UDP 代理服务器。它采用事件驱动模型,适合处理大量并发连接,并且配置简单、资源占用相对较低,因此经常被部署在 Web 系统的最外层。

Nginx 能做什么

常见能力包括:

  • Web Server:直接提供 HTML、CSS、JavaScript、图片等静态资源。
  • 反向代理:将客户端请求转发到 Spring Boot、Tomcat、Node.js 等后端应用。
  • 负载均衡:在多个后端实例之间分配请求。
  • HTTPS/TLS 终止:统一处理证书和 TLS 握手,后端应用可以只提供 HTTP。
  • HTTP 自动跳转 HTTPS:将 80 端口请求统一重定向到 443。
  • 动静分离:静态资源由 Nginx 直接处理,动态请求交给后端。
  • 缓存:缓存后端响应,减少源站压力。
  • 域名和虚拟主机:一台 Nginx 可以根据域名代理多个系统。
  • WebSocket 代理:支持 WebSocket、SSE 等长连接场景。

一个典型的生产请求链路

1
2
3
4
5
6
7
8
9
10
11
12
Browser / App
|
| HTTP :80 / HTTPS :443
v
+-----------------------+
| Nginx |
| TLS / Redirect / Proxy|
+-----------------------+
| |
| +------------------> Static Files
|
+--------------------------------> Spring Boot / Tomcat / API

在 Docker 中,Nginx 和 Web 应用通常是不同容器;在 Kubernetes 中,通常由 Gateway/Ingress 作为集群入口,Service 再把流量送到 Web Pod。


安装部署

Docker 方式安装

如果只是快速启动一个 Nginx:

1
2
3
4
5
6
docker run -d \
--name nginx \
--restart unless-stopped \
-p 80:80 \
-p 443:443 \
nginx:stable-alpine

查看默认配置:

1
docker exec -it nginx nginx -T

检查配置:

1
docker exec nginx nginx -t

重新加载配置:

1
docker exec nginx nginx -s reload

生产环境不建议只运行一个“裸 Nginx 容器”,而是应该将配置文件、证书和日志通过 volume 挂载,并且固定镜像版本或 digest,避免镜像标签变化导致不可预期升级。

Linux 软件包安装

在 Rocky Linux、RHEL、AlmaLinux 等系统上,可以优先使用系统软件包或 Nginx 官方仓库,而不是每次都从源码编译。

1
2
sudo dnf install -y nginx
sudo systemctl enable --now nginx

查看版本和编译参数:

1
2
nginx -v
nginx -V

检查配置:

1
sudo nginx -t

重新加载:

1
sudo systemctl reload nginx

源码编译安装

如果确实需要自定义模块,再考虑源码编译。

安装依赖:

1
sudo dnf install -y gcc make openssl openssl-devel zlib zlib-devel pcre2 pcre2-devel

解压源码后:

1
2
3
4
5
6
7
./configure \
--prefix=/usr/local/nginx \
--with-http_ssl_module \
--with-http_stub_status_module

make -j"$(nproc)"
sudo make install

这里需要特别注意:配置脚本是 ./configure,不是 ./config

如果通过源码编译 Nginx,并且要使用 HTTPS,需要启用 --with-http_ssl_module。大部分发行版提供的 Nginx 软件包以及官方 Docker 镜像已经具备 HTTPS 能力,不需要重新编译。

检查当前 Nginx 是否编译了 SSL 模块:

1
nginx -V 2>&1 | grep -- --with-http_ssl_module

Nginx 配置文件结构

Nginx 主配置文件通常是:

1
/etc/nginx/nginx.conf

常见额外配置目录:

1
/etc/nginx/conf.d/*.conf

源码安装时通常位于:

1
/usr/local/nginx/conf/nginx.conf

Nginx 配置主要包含三层:

  • 全局块:worker、PID、日志、运行用户等。
  • events:连接和事件模型配置。
  • http:HTTP Server、虚拟主机、反向代理、缓存、压缩等配置。

一个比较实用的基础配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
user nginx;
worker_processes auto;
worker_rlimit_nofile 65535;
pid /var/run/nginx.pid;

error_log /var/log/nginx/error.log warn;

events {
worker_connections 4096;
multi_accept on;
}

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time urt=$upstream_response_time';

access_log /var/log/nginx/access.log main;

sendfile on;
tcp_nopush on;
tcp_nodelay on;

keepalive_timeout 65;
server_tokens off;

client_max_body_size 20M;
client_body_timeout 30s;
client_header_timeout 30s;

gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 1k;
gzip_types
text/plain
text/css
application/json
application/javascript
application/xml
image/svg+xml;

include /etc/nginx/conf.d/*.conf;
}

worker_connections 并不等于整个 Nginx 的最大并发量。实际容量还受到 worker 数量、文件描述符、上游连接、Keep-Alive、操作系统网络参数和业务响应时间等因素影响。


Nginx 接入 HTTPS

HTTPS 在 Nginx 中到底做了什么

最常见的架构是让 Nginx 做 TLS Termination:

1
2
3
4
5
6
7
8
9
Client
|
| HTTPS
v
Nginx :443
|
| HTTP(内网)
v
Web Application :8080

这样证书只需要部署在 Nginx 或 Kubernetes Gateway 上,后面的 Spring Boot/Tomcat 不需要每个实例都配置证书。

如果安全要求更高,也可以让 Nginx 到后端之间继续使用 HTTPS,甚至使用 mTLS,但普通企业 Web 系统一般先从入口 TLS 终止开始。

HTTPS 的前置条件

正式部署 HTTPS 前至少需要:

  1. 一个域名,例如 example.com
  2. DNS 已经解析到服务器或负载均衡入口。
  3. 防火墙、安全组允许 80 和 443。
  4. 一张与域名匹配的证书。
  5. Nginx 具备 SSL 模块。

对于公网生产环境,应使用受浏览器信任的 CA 签发证书,例如企业购买的证书或 ACME/Let’s Encrypt 等自动签发证书。

自签名证书适合开发、实验、内网测试。自签名不代表通信没有加密,但浏览器默认不信任签发者,因此会显示证书不受信任警告。


开发环境生成自签名证书

引用文章中使用 OpenSSL 生成了私钥、CSR 和自签名 CRT,这个思路可以用于测试,但不建议继续使用 1024 位 RSA。

测试环境可以直接生成 RSA 2048 自签名证书:

1
2
3
4
5
6
7
8
9
10
11
12
mkdir -p /etc/nginx/ssl
cd /etc/nginx/ssl

openssl req -x509 \
-nodes \
-newkey rsa:2048 \
-sha256 \
-days 365 \
-keyout server.key \
-out server.crt \
-subj "/C=CN/ST=Shanghai/L=Shanghai/O=Example/OU=Dev/CN=example.com" \
-addext "subjectAltName=DNS:example.com,DNS:www.example.com"

生成:

1
2
/etc/nginx/ssl/server.key
/etc/nginx/ssl/server.crt

私钥一定不要提交到 Git:

1
chmod 600 /etc/nginx/ssl/server.key

关于 CSR

如果证书由企业 CA 或公网 CA 签发,常见流程是:

1
2
3
4
5
openssl genrsa -out example.com.key 2048

openssl req -new \
-key example.com.key \
-out example.com.csr

将 CSR 交给 CA,最终获得证书链文件。

实际部署时,很多 CA 会直接提供类似:

1
2
fullchain.pem
privkey.pem

其中 fullchain.pem 一般包含服务器证书和中间证书链,privkey.pem 是私钥。


最基础的 HTTPS 配置

创建:

1
/etc/nginx/conf.d/example.conf

配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server {
listen 443 ssl;
listen [::]:443 ssl;

server_name example.com www.example.com;

ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;

root /usr/share/nginx/html;
index index.html;

location / {
try_files $uri $uri/ =404;
}
}

如果使用前面的自签名证书,则改成:

1
2
ssl_certificate     /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;

测试配置:

1
sudo nginx -t

重新加载:

1
sudo systemctl reload nginx

测试:

1
curl -I https://example.com

自签名证书可以临时使用:

1
curl -kI https://example.com

查看 TLS 握手和证书链:

1
2
3
4
openssl s_client \
-connect example.com:443 \
-servername example.com \
-showcerts

HTTP 自动跳转 HTTPS

只启用 443 并不会让用户访问 http://example.com 时自动进入 HTTPS。最稳妥的方式是单独保留一个监听 80 的 server,只负责重定向。

网站场景:301 跳转

1
2
3
4
5
6
7
8
server {
listen 80;
listen [::]:80;

server_name example.com www.example.com;

return 301 https://example.com$request_uri;
}

例如:

1
http://example.com/user?id=100

会变成:

1
https://example.com/user?id=100

$request_uri 会保留路径和 QueryString。

如果希望 www.example.comexample.com 都保持原 Host:

1
return 301 https://$host$request_uri;

如果这是公网系统,并且 server_name 是固定域名,使用固定的规范域名通常更加清晰,也可以顺手完成 www 到主域名的统一。

API 场景:优先考虑 308

301 在不同客户端中可能涉及请求方法变化。对于需要保留 POST/PUT/PATCH 等请求方法和请求体的 API,永久跳转更适合使用 308:

1
2
3
4
5
6
server {
listen 80;
server_name api.example.com;

return 308 https://api.example.com$request_uri;
}

因此可以简单理解:

  • 普通网站:301 很常见。
  • API:更关注方法和请求体保持时,使用 308 更稳妥。

HTTPS + 反向代理 Web 应用

最常见的生产部署方式不是让 Nginx 自己返回页面,而是 Nginx 接收 HTTPS,再把请求代理到 Spring Boot/Tomcat。

假设后端地址:

1
127.0.0.1:8080

配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
upstream web_backend {
server 127.0.0.1:8080;
}

server {
listen 80;
server_name example.com;

return 308 https://example.com$request_uri;
}

server {
listen 443 ssl;
server_name example.com;

ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;

location / {
proxy_pass http://web_backend;
proxy_http_version 1.1;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;

proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}

此时调用链:

1
2
3
4
5
6
7
8
https://example.com
|
v
Nginx :443
|
| HTTP
v
Spring Boot :8080

浏览器看到的是 HTTPS,而内部 Web 应用只需要监听 8080。


X-Forwarded-* 为什么重要

TLS 在 Nginx 结束之后,后端真正收到的连接可能是:

1
http://127.0.0.1:8080

如果不传递代理 Header,后端可能误以为原始请求就是 HTTP,导致:

  • OAuth2 回调地址生成成 http://
  • Spring Security 重定向 URL 错误。
  • OpenAPI/Swagger Server URL 错误。
  • 登录成功后跳转到错误协议。
  • 获取不到客户端真实 IP。

因此建议至少传递:

1
2
3
4
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

Spring Boot 项目如果需要根据这些 Header 正确识别原始协议,可以根据实际部署方式启用 Forwarded Header 支持,例如:

1
server.forward-headers-strategy=framework

前提是这些 Header 只能由可信反向代理注入,不能无条件信任来自公网客户端的伪造 Header。


proxy_pass 末尾 / 的坑

这可能是 Nginx 最容易踩的坑之一。

配置一:

1
2
3
location /api/ {
proxy_pass http://127.0.0.1:8080;
}

请求:

1
/api/user/1

后端收到:

1
/api/user/1

配置二:

1
2
3
location /api/ {
proxy_pass http://127.0.0.1:8080/;
}

请求:

1
/api/user/1

后端收到:

1
/user/1

区别就是 proxy_pass 后面的 / 会影响 URI 替换规则。


WebSocket 代理

如果项目包含 WebSocket,可以在 http 块中增加:

1
2
3
4
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

然后在对应 location 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
location /ws/ {
proxy_pass http://web_backend;
proxy_http_version 1.1;

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

proxy_read_timeout 3600s;
}

HSTS:让浏览器以后只访问 HTTPS

确认 HTTPS 已经稳定运行后,可以考虑启用 HSTS:

1
add_header Strict-Transport-Security "max-age=31536000" always;

如果所有子域名也永远支持 HTTPS,再考虑:

1
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

HSTS 一旦被浏览器缓存,在有效期内浏览器会主动拒绝 HTTP。不要在 HTTPS 还没有完全验证、部分子域名还需要 HTTP、或者证书管理流程不稳定时贸然启用 includeSubDomains 或 preload。


Docker 场景:Nginx + Web 应用如何部署

在 Docker 中,一个非常推荐的方式是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Internet
|
80 / 443
|
v
+----------------+
| nginx container|
+----------------+
|
| Docker Network
v
+----------------+
| web container |
| :8080 |
+----------------+

只有 Nginx 暴露 80/443,Web 应用不直接暴露到公网。

目录结构

1
2
3
4
5
6
7
8
nginx-web/
├── docker-compose.yml
└── nginx/
├── conf.d/
│ └── web.conf
└── ssl/
├── fullchain.pem
└── privkey.pem

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
services:
web:
image: registry.example.com/example-web:1.0.0
restart: unless-stopped
expose:
- "8080"
networks:
- app-network

nginx:
image: nginx:stable-alpine
restart: unless-stopped
depends_on:
- web
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- nginx-logs:/var/log/nginx
networks:
- app-network

networks:
app-network:
driver: bridge

volumes:
nginx-logs:

这里故意不给 web 配置:

1
2
ports:
- "8080:8080"

因为正常情况下外部用户不应该绕过 Nginx 直接访问 Web 容器。

expose 只是表达容器内部服务端口,容器之间通过 Compose 网络通信。

Docker 中的 Nginx 配置

nginx/conf.d/web.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
upstream web_backend {
server web:8080;
}

server {
listen 80;
server_name example.com;

return 308 https://example.com$request_uri;
}

server {
listen 443 ssl;
server_name example.com;

ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;

client_max_body_size 20M;

location / {
proxy_pass http://web_backend;
proxy_http_version 1.1;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

proxy_connect_timeout 5s;
proxy_read_timeout 60s;
}
}

这里最重要的是:

1
server web:8080;

web 是 Docker Compose 的 Service Name。Nginx 通过 Docker 网络 DNS 找到 Web 容器。

不要写:

1
server 127.0.0.1:8080;

因为在 Nginx 容器中,127.0.0.1 指向的是 Nginx 容器自己,而不是 Web 容器。

启动:

1
docker compose up -d

查看:

1
docker compose ps

检查 Nginx 配置:

1
docker compose exec nginx nginx -t

重新加载:

1
docker compose exec nginx nginx -s reload

日志:

1
2
3
docker compose logs -f nginx

docker compose logs -f web

Docker 场景:前端 SPA + 后端 API

对于 React/Vue 等前后端分离项目,也经常让 Nginx 同时负责:

  • /:前端静态资源。
  • /api/:代理后端 API。

配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
upstream api_backend {
server api:8080;
}

server {
listen 80;
server_name example.com;

return 308 https://example.com$request_uri;
}

server {
listen 443 ssl;
server_name example.com;

ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;

root /usr/share/nginx/html;
index index.html;

location / {
try_files $uri $uri/ /index.html;
}

location /api/ {
proxy_pass http://api_backend;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?)$ {
expires 7d;
add_header Cache-Control "public";
try_files $uri =404;
}
}

对于 SPA 项目:

1
try_files $uri $uri/ /index.html;

非常重要,否则访问:

1
https://example.com/user/100

并刷新页面时,Nginx 会尝试寻找 /user/100 文件,从而返回 404。


Docker 场景常见问题

1. 容器中不要使用 127.0.0.1 找另一个容器

错误:

1
proxy_pass http://127.0.0.1:8080;

正确:

1
proxy_pass http://web:8080;

2. 不要把私钥打进镜像

不要:

1
COPY privkey.pem /etc/nginx/ssl/

生产环境应使用 Secret、volume、外部证书系统或发布平台注入。

3. 后端端口尽量不要直接暴露公网

如果所有请求都必须经过 Nginx,就不要额外映射:

1
8080:8080

否则攻击者可能绕开 Nginx 的 TLS、鉴权、WAF、限流等入口规则。

4. 修改配置后先 nginx -t

1
docker compose exec nginx nginx -t

确认成功后再 reload。


Kubernetes 场景:Web 项目应该怎么部署

Kubernetes 中不要简单照搬 Docker Compose 的思路,在每个 Web Pod 前面再塞一个独立入口 Nginx。

更常见的结构是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Internet
|
v
LoadBalancer / Gateway
|
| TLS Termination
v
Gateway / Route
|
v
Service (ClusterIP)
|
+----------+----------+
| | |
v v v
Web Pod Web Pod Web Pod
:8080 :8080 :8080

如果是 React/Vue 静态前端,前端本身仍然可以用一个 Nginx 容器来提供静态文件,但外部 HTTPS/TLS 最好统一由 Kubernetes 的 Gateway/入口层处理。


先部署 Web Deployment

以下以 Spring Boot Web 服务为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
apiVersion: apps/v1
kind: Deployment
metadata:
name: example-web
namespace: web-prod
spec:
replicas: 3
revisionHistoryLimit: 5
selector:
matchLabels:
app: example-web
template:
metadata:
labels:
app: example-web
spec:
containers:
- name: web
image: registry.example.com/example-web:1.0.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
env:
- name: TZ
value: Asia/Shanghai
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: http
initialDelaySeconds: 30
periodSeconds: 20
timeoutSeconds: 3
failureThreshold: 3
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "2"
memory: "1Gi"

这里的健康检查路径是 Spring Boot Actuator 示例,需要应用实际启用对应 Probe;如果不是 Spring Boot,改成项目真实存在的健康检查路径。


创建 ClusterIP Service

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: example-web
namespace: web-prod
spec:
type: ClusterIP
selector:
app: example-web
ports:
- name: http
port: 80
targetPort: http

此时集群内部可以通过:

1
http://example-web.web-prod.svc.cluster.local

访问应用。

外部不需要直接访问 Pod IP,也不需要给每个 Pod 配公网端口。


Kubernetes HTTPS:TLS Secret

如果已经有证书:

1
2
3
4
kubectl create secret tls example-com-tls \
--cert=fullchain.pem \
--key=privkey.pem \
-n web-prod

查看:

1
kubectl get secret example-com-tls -n web-prod

Secret 应该由 Kubernetes RBAC、GitOps Secret 管理方案、Vault 或其他 Secret 管理系统保护,不要把私钥明文写入 Git 仓库。


Kubernetes 入口选择:2026 年的新项目优先 Gateway API

过去大量教程会直接安装社区版 ingress-nginx,然后创建 Ingress 资源。

需要注意的是:Kubernetes 社区的 Ingress NGINX 项目已经在 2026 年 3 月退役,退役后不会再提供新的 bugfix 和安全更新。与此同时,Kubernetes 官方文档已经明确推荐使用 Gateway API 替代 Ingress 作为新项目的演进方向。

这里需要区分三个概念:

1
2
3
4
Ingress API          Kubernetes 的 API 资源,仍然存在,但 API 已冻结
Ingress Controller 实现 Ingress 的控制器,有多种产品
Ingress NGINX Kubernetes 社区过去维护的一种 Ingress Controller,已退役
Gateway API Kubernetes 新一代流量入口 API

所以:

  • 老集群:可以继续维护现有 Ingress,但应评估控制器迁移。
  • 新项目:优先考虑 Gateway API。
  • 具体 GatewayClass 由你的集群网络/网关实现决定。

可以先查看集群已有 GatewayClass:

1
kubectl get gatewayclass

Kubernetes Gateway API:HTTPS + HTTP 自动跳 HTTPS

下面给出一个通用 Gateway API 示例。

前提:

  • 集群已经安装 Gateway API CRD。
  • 已经部署一个支持 Gateway API 的 Controller。
  • kubectl get gatewayclass 能看到可使用的 GatewayClass。
  • 已经创建 example-com-tls TLS Secret。

假设 GatewayClass 名称为:

1
example-gateway-class

请根据自己的集群替换。

Gateway

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: example-gateway
namespace: web-prod
spec:
gatewayClassName: example-gateway-class
listeners:
- name: http
protocol: HTTP
port: 80
hostname: example.com
allowedRoutes:
namespaces:
from: Same

- name: https
protocol: HTTPS
port: 443
hostname: example.com
tls:
mode: Terminate
certificateRefs:
- kind: Secret
group: ""
name: example-com-tls
allowedRoutes:
namespaces:
from: Same

这里 HTTPS 的 TLS 会在 Gateway 入口终止,后面的 Service 仍然可以使用普通 HTTP。


HTTP -> HTTPS 自动跳转

创建一个只绑定 http listener 的 HTTPRoute:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: example-http-redirect
namespace: web-prod
spec:
parentRefs:
- name: example-gateway
sectionName: http
hostnames:
- example.com
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301

效果:

1
http://example.com/user/100

会跳到:

1
https://example.com/user/100

Gateway API 也支持 307/308 等方法保持型重定向,但具体实现需要检查 Gateway Controller 的支持特性。普通网站使用 301 已经足够;API 场景需要严格保持请求方法时,再确认 Controller 支持 308 后使用 308。


HTTPS 请求转发到 Web Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: example-web
namespace: web-prod
spec:
parentRefs:
- name: example-gateway
sectionName: https
hostnames:
- example.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: example-web
port: 80

最终链路:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Browser
|
| HTTP :80
+----------------> HTTPRoute Redirect -> HTTPS
|
| HTTPS :443
v
Gateway
|
| HTTP
v
Service example-web:80
|
v
Pod :8080

Kubernetes:前端和后端同时部署

如果系统是:

1
2
3
4
5
/
-> frontend

/api/
-> backend

可以准备两个 Service:

1
2
frontend-service:80
backend-service:80

然后让 Gateway 统一路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: example-app
namespace: web-prod
spec:
parentRefs:
- name: example-gateway
sectionName: https
hostnames:
- example.com
rules:
- matches:
- path:
type: PathPrefix
value: /api/
backendRefs:
- name: backend-service
port: 80

- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: frontend-service
port: 80

这样不需要额外在外部再部署一层 Nginx 做路径分发。

如果 frontend-service 本身是一个 Nginx Pod,它只负责静态资源即可;TLS、域名和公网入口由 Gateway 负责。


Kubernetes 旧方案:Ingress 怎么写

Ingress API 本身并没有被删除,已有集群仍然大量使用。

一个基础 HTTPS Ingress:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-web
namespace: web-prod
spec:
ingressClassName: your-ingress-class
tls:
- hosts:
- example.com
secretName: example-com-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: example-web
port:
number: 80

注意:

1
ingressClassName: your-ingress-class

必须替换成集群实际存在的 IngressClass:

1
kubectl get ingressclass

仅仅创建 Ingress Resource 并不会自动产生入口流量,集群必须已经存在对应的 Ingress Controller。

不同 Controller 的 HTTP -> HTTPS 强制跳转配置并不完全一样,很多时候依赖 Controller 自己的 Annotation,因此不能把某一家 Controller 的注解当成 Kubernetes Ingress 标准能力。

如果现有老集群仍使用已经退役的社区 ingress-nginx,常见配置曾经是:

1
2
3
4
metadata:
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"

这段只适用于相应 Controller,不应该复制到其他 Ingress Controller,更不建议为了使用这两个注解而在新集群中重新引入已退役的 ingress-nginx。


Kubernetes 自动签发和续期证书

生产集群不建议靠人工复制证书然后等到过期前再手工替换。

常见方案是使用 cert-manager 对接:

  • Let’s Encrypt / ACME。
  • 企业内部 CA。
  • Vault PKI。
  • 其他支持的证书签发系统。

例如由 Certificate 资源维护证书:

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-com
namespace: web-prod
spec:
secretName: example-com-tls
dnsNames:
- example.com
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer

签发成功后,cert-manager 将证书和私钥写入:

1
example-com-tls

Gateway 再引用这个 Secret:

1
2
3
4
5
tls:
certificateRefs:
- kind: Secret
group: ""
name: example-com-tls

这样 Gateway 与证书签发职责分离:

1
2
3
4
5
6
7
8
cert-manager
|
| create / renew
v
TLS Secret
|
v
Gateway :443

ClusterIssuer 中 ACME Solver 的写法与 DNS、Gateway Controller、云平台和网络环境有关,应根据实际环境配置,不建议直接复制一份固定模板到所有集群。


Docker 与 Kubernetes 场景怎么选

Docker / Docker Compose

适合:

  • 单机部署。
  • 中小型内部系统。
  • 开发/测试环境。
  • 服务数量不多。
  • 暂时不需要复杂弹性扩缩容。

典型结构:

1
2
3
4
Nginx Container
|
v
Web Container

Nginx 同时负责:

  • HTTPS。
  • HTTP -> HTTPS。
  • 域名。
  • 反向代理。
  • 静态文件。

Kubernetes

适合:

  • 多节点。
  • 多副本。
  • 滚动升级。
  • 自动扩缩容。
  • 多服务统一入口。
  • 更完整的云原生治理。

典型结构:

1
2
3
4
5
Gateway
|
Service
|
Deployment / Pods

Gateway/入口层负责:

  • HTTPS。
  • HTTP -> HTTPS。
  • 域名。
  • 路由。

应用 Pod 负责:

  • 业务逻辑。
  • 健康检查。
  • 应用自身指标。

不要让每个业务 Pod 都重复保存同一份公网 TLS 私钥,除非明确需要端到端 TLS 或 mTLS。


动静分离

动静分离的核心思想是:静态资源尽量由 Nginx 直接返回,动态请求交给后端应用。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server {
listen 80;
server_name example.com;

location /static/ {
alias /var/www/static/;
expires 7d;
add_header Cache-Control "public";
}

location / {
proxy_pass http://127.0.0.1:8080;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

这里使用:

1
alias /var/www/static/;

请求:

1
/static/logo.png

对应:

1
/var/www/static/logo.png

rootalias 的路径拼接方式不同,配置静态目录时要特别注意。


反向代理

反向代理是客户端访问 Nginx,Nginx 再代表客户端访问真实后端服务。

客户端看到:

1
https://example.com

真正的后端可能是:

1
2
3
10.0.0.11:8080
10.0.0.12:8080
10.0.0.13:8080

基本配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
upstream backend {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
server 10.0.0.13:8080;
}

server {
listen 80;
server_name example.com;

location / {
proxy_pass http://backend;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

反向代理可以实现:

  • 隐藏后端真实地址。
  • TLS 统一终止。
  • 多实例负载均衡。
  • 缓存。
  • 超时控制。
  • 限流。
  • 域名和路径路由。

负载均衡

默认轮询

1
2
3
4
5
upstream backend {
server backend1.example.com;
server backend2.example.com;
server backend3.example.com;
}

权重

1
2
3
4
5
upstream backend {
server backend1.example.com weight=3;
server backend2.example.com weight=1;
server backend3.example.com weight=2;
}

最少连接

1
2
3
4
5
6
7
upstream backend {
least_conn;

server backend1.example.com;
server backend2.example.com;
server backend3.example.com;
}

IP Hash

1
2
3
4
5
6
7
upstream backend {
ip_hash;

server backend1.example.com;
server backend2.example.com;
server backend3.example.com;
}

ip_hash 可以提高同一个客户端落到相同后端的概率,但在现代应用中,更推荐让 Session 外置到 Redis、数据库或 Token 中,使 Web 实例本身无状态,而不是依赖负载均衡层维持会话粘滞。

基础失败检测

1
2
3
4
upstream backend {
server backend1.example.com max_fails=3 fail_timeout=30s;
server backend2.example.com max_fails=3 fail_timeout=30s;
}

Nginx 缓存

Nginx 可以缓存反向代理响应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
http {
proxy_cache_path /var/cache/nginx \
levels=1:2 \
keys_zone=my_cache:100m \
inactive=60m \
max_size=10g;

server {
listen 80;
server_name example.com;

location /api/public/ {
proxy_cache my_cache;
proxy_cache_key "$scheme$request_method$host$request_uri";

proxy_cache_valid 200 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating;

add_header X-Cache-Status $upstream_cache_status always;

proxy_pass http://backend;
}
}
}

缓存不要无脑加在所有 API 上,尤其是:

  • 用户个人数据。
  • 权限相关接口。
  • 支付和订单接口。
  • 实时库存。
  • 响应依赖 Cookie/Authorization 的接口。

否则“性能优化”很可能瞬间升级成“数据串号体验”。

清除缓存

不建议暴露一个类似:

1
/clear_cache

的公网接口然后执行 rm -rf

更稳妥的做法是:

  • 调整 cache key 和 cache version。
  • 通过运维脚本清理缓存目录。
  • 配置权限受控的缓存失效机制。
  • 使用支持精细 Purge 的产品或模块。

简单运维场景可以在确认 Nginx 已停止访问缓存文件后,通过系统命令清理对应缓存目录,再重新加载服务。


HTTPS 混合内容问题

即使入口已经是 HTTPS,如果页面中仍然写死:

1
2
http://api.example.com
http://cdn.example.com/logo.png

浏览器仍然可能报 Mixed Content,并拒绝加载部分资源。

因此切换 HTTPS 时要同时检查:

  • 前端 API Base URL。
  • 图片、CSS、JS、字体地址。
  • CDN 地址。
  • WebSocket 地址,ws:// 是否需要改成 wss://
  • OAuth2/SSO Callback URL。
  • 第三方回调。
  • Spring Boot 生成的绝对 URL。

不要只改 Nginx 的 443 配置,就认为整个系统已经完成 HTTPS 改造。


HTTPS 证书更新

证书不是“配置一次永久有效”。

生产环境至少应该监控:

  • 证书过期时间。
  • 自动续期是否成功。
  • 私钥和证书是否匹配。
  • 中间证书链是否完整。
  • 新证书更新后 Nginx/Gateway 是否已经重新加载。

对于裸机 Nginx,更新证书后通常执行:

1
nginx -t && systemctl reload nginx

Docker:

1
2
docker compose exec nginx nginx -t && \
docker compose exec nginx nginx -s reload

Kubernetes 则更适合交给 cert-manager 或云平台证书系统自动维护 Secret 和入口配置。


防火墙开放 HTTP/HTTPS

Rocky Linux/RHEL 使用 firewalld 时:

1
2
3
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

查看:

1
sudo firewall-cmd --list-all

云服务器还要同时检查云厂商安全组或防火墙规则。


常用排障命令

Nginx

检查配置:

1
nginx -t

打印全部生效配置:

1
nginx -T

查看进程:

1
ps -ef | grep nginx

查看监听端口:

1
ss -lntp | grep nginx

查看日志:

1
2
tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log

HTTP/HTTPS

1
2
curl -I http://example.com
curl -I https://example.com

确认 HTTP 是否跳转 HTTPS:

1
curl -I http://example.com

预期类似:

1
2
HTTP/1.1 301 Moved Permanently
Location: https://example.com/

或者:

1
2
HTTP/1.1 308 Permanent Redirect
Location: https://example.com/

查看完整跳转链:

1
curl -IL http://example.com

TLS

1
2
3
openssl s_client \
-connect example.com:443 \
-servername example.com

重点检查:

  • Subject/SAN 是否包含域名。
  • Issuer 是否正确。
  • Verify return code 是否为 0。
  • 证书链是否完整。

Docker

1
2
3
docker compose ps
docker compose logs -f nginx
docker compose exec nginx nginx -t

验证 Nginx 容器能否访问 Web:

1
docker compose exec nginx wget -S -O- http://web:8080/

Kubernetes

1
2
3
4
kubectl get pods -n web-prod
kubectl get svc -n web-prod
kubectl get gateway -n web-prod
kubectl get httproute -n web-prod

查看状态:

1
2
kubectl describe gateway example-gateway -n web-prod
kubectl describe httproute example-web -n web-prod

查看 Endpoint:

1
kubectl get endpointslices -n web-prod

如果 Service 没有 Endpoint,先排查 Pod Label 和 Service Selector,不要第一时间怀疑 Gateway。


常见错误总结

Nginx 配置成功但 443 访问不了

检查:

1
ss -lntp | grep 443

再检查:

  • Nginx 是否加载了新配置。
  • 防火墙是否开放 443。
  • 云安全组是否开放 443。
  • 证书路径是否正确。
  • 私钥权限是否允许 Nginx 读取。

HTTP 没有自动跳 HTTPS

确认存在单独的 80 端口 Server:

1
2
3
4
5
server {
listen 80;
server_name example.com;
return 308 https://example.com$request_uri;
}

Docker Nginx 报 502

检查:

1
docker compose ps

然后进入 Nginx 容器访问后端:

1
docker compose exec nginx wget -S -O- http://web:8080/

最常见原因:

  • 写成 127.0.0.1:8080
  • Service Name 写错。
  • Web 容器实际没启动。
  • Web 只监听 127.0.0.1 而不是容器的 0.0.0.0
  • 后端端口不是 8080。

Kubernetes Gateway 有地址但访问 503

优先检查:

1
kubectl get svc,pod,endpointslices -n web-prod

如果 Service 后面没有健康 Endpoint,Gateway 再正常也无法把请求交给应用。

浏览器提示证书不安全

常见原因:

  • 使用自签名证书。
  • 域名和 SAN 不匹配。
  • 证书过期。
  • 中间证书缺失。
  • 客户端不信任内部 CA。

一份完整的单机生产配置示例

假设:

1
2
3
4
域名:example.com
后端:127.0.0.1:8080
证书:/etc/nginx/ssl/fullchain.pem
私钥:/etc/nginx/ssl/privkey.pem

完整示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
upstream web_backend {
least_conn;

server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;

keepalive 32;
}

server {
listen 80;
listen [::]:80;

server_name example.com www.example.com;

return 308 https://example.com$request_uri;
}

server {
listen 443 ssl;
listen [::]:443 ssl;

server_name example.com;

ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;

server_tokens off;
client_max_body_size 20M;

access_log /var/log/nginx/example.access.log main;
error_log /var/log/nginx/example.error.log warn;

location / {
proxy_pass http://web_backend;
proxy_http_version 1.1;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;

proxy_set_header Connection "";

proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}

如果确认全站已经稳定支持 HTTPS,再根据实际情况增加 HSTS。


最终部署建议

如果是单机 Docker:

1
2
3
4
5
6
7
Internet
|
Nginx Container :80/:443
|
Docker Network
|
Web Container :8080

建议:

  • 只有 Nginx 暴露公网端口。
  • Nginx 负责 HTTPS 和 HTTP -> HTTPS。
  • 证书通过只读 volume 挂载。
  • Web 通过 Compose Service Name 访问。
  • 修改配置先 nginx -t

如果是 Kubernetes:

1
2
3
4
5
6
7
Internet
|
Gateway
|
Service
|
Web Pods

建议:

  • 新项目优先 Gateway API。
  • TLS 在 Gateway 终止。
  • HTTPRoute 负责 HTTP -> HTTPS。
  • Web Service 使用 ClusterIP。
  • Pod 不直接暴露公网。
  • 证书交给 Secret + cert-manager/外部证书系统管理。
  • 已有 Ingress 可以继续使用,但应确认 Controller 的维护状态,不要把已经退役的 ingress-nginx 作为新集群默认选择。

参考资料

  • Nginx 官方文档:Module ngx_http_ssl_module
  • Nginx 官方文档:Configuring HTTPS servers
  • Nginx 官方文档:Reverse Proxy
  • Docker 官方文档:Compose networking
  • Kubernetes 官方文档:Ingress
  • Kubernetes 官方文档:Gateway API
  • Gateway API 官方文档:HTTP redirects and rewrites
  • Gateway API 官方文档:TLS Configuration
  • Kubernetes Blog:Ingress NGINX Retirement
  • cert-manager 官方文档
  • 《Nginx配置免费HTTPS详细教程》:https://blog.csdn.net/XiaoXiaoYunXing/article/details/134440485

启示录

富贵岂由人,时会高志须酬。

能成功于千载者,必以近察远。


Nginx 使用简明指南
https://allendericdalexander.github.io/2025/03/06/devops/web/nginx-https-docker-kubernetes-guide/
作者
AtLuoFu
发布于
2025年3月6日
许可协议