Nginx 反向代理 + Let's Encrypt HTTPS 完整配置流程,涵盖 HTTP 临时反代、Certbot 证书申请、HTTPS 最终配置、FRP 穿透场景及常见错误排查。

Nginx 反向代理配置指南
5 mins
1098 words
Loading views

一、目标结构h2

本文用于将一个本机端口上的 Web 服务,通过 Nginx 反向代理到指定域名,并配置 HTTPS。

目标结构:

https://<SERVICE_DOMAIN>
Nginx 443
http://127.0.0.1:<BACKEND_PORT>
后端服务

示例占位符:

<SERVICE_DOMAIN> = app.example.com
<BACKEND_PORT> = 18090
<SERVICE_NAME> = app

二、前置检查h2

确认域名已经解析到当前服务器:

Terminal window
dig +short <SERVICE_DOMAIN>
curl -4s ifconfig.me

两者返回的 IP 应该一致。

确认后端服务在服务器本机可访问:

Terminal window
curl -I http://127.0.0.1:<BACKEND_PORT>

正常情况下应返回类似:

HTTP/1.1 200 OK

或者:

HTTP/1.1 302 Found
HTTP/1.1 401 Unauthorized

只要能返回 HTTP 响应,就说明后端服务可用。

确认端口监听:

Terminal window
ss -lntp | grep <BACKEND_PORT>

三、安装 Nginx 和 Certboth2

Terminal window
apt update
apt install -y nginx certbot python3-certbot-nginx
systemctl enable --now nginx

放行 80 和 443:

Terminal window
ufw allow 80/tcp
ufw allow 443/tcp

如果不用 ufw,就在服务器安全组或防火墙中放行:

TCP 80
TCP 443

四、创建 HTTP 临时反代配置h2

先创建 HTTP 配置,用于测试反代和申请证书。

Terminal window
cat > /etc/nginx/sites-available/<SERVICE_DOMAIN> <<'EOF'
server {
listen 80;
listen [::]:80;
server_name <SERVICE_DOMAIN>;
access_log /var/log/nginx/<SERVICE_DOMAIN>.access.log;
error_log /var/log/nginx/<SERVICE_DOMAIN>.error.log;
location / {
proxy_pass http://127.0.0.1:<BACKEND_PORT>;
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_redirect off;
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
EOF

启用站点:

Terminal window
ln -sf /etc/nginx/sites-available/<SERVICE_DOMAIN> /etc/nginx/sites-enabled/<SERVICE_DOMAIN>

测试配置:

Terminal window
nginx -t

通过后重载:

Terminal window
systemctl reload nginx

测试 HTTP 访问:

Terminal window
curl -I http://<SERVICE_DOMAIN>

如果返回后端服务响应,说明 HTTP 反代正常。


五、申请 HTTPS 证书h2

Terminal window
certbot --nginx -d <SERVICE_DOMAIN>

如果提示是否将 HTTP 跳转到 HTTPS,建议选择跳转。

测试证书续期:

Terminal window
certbot renew --dry-run

六、最终 HTTPS 反代配置h2

证书申请成功后,覆盖成最终配置。

Terminal window
cat > /etc/nginx/sites-available/<SERVICE_DOMAIN> <<'EOF'
upstream <SERVICE_NAME>_backend {
server 127.0.0.1:<BACKEND_PORT>;
keepalive 32;
}
server {
listen 80;
listen [::]:80;
server_name <SERVICE_DOMAIN>;
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name <SERVICE_DOMAIN>;
ssl_certificate /etc/letsencrypt/live/<SERVICE_DOMAIN>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<SERVICE_DOMAIN>/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
access_log /var/log/nginx/<SERVICE_DOMAIN>.access.log;
error_log /var/log/nginx/<SERVICE_DOMAIN>.error.log;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
gzip on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_vary on;
gzip_proxied any;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml
image/svg+xml;
location ~* \.(css|js|mjs|png|jpg|jpeg|gif|ico|svg|webp|woff|woff2|ttf)$ {
proxy_pass http://<SERVICE_NAME>_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
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-Port $server_port;
proxy_redirect off;
expires 7d;
add_header Cache-Control "public, max-age=604800" always;
}
location / {
proxy_pass http://<SERVICE_NAME>_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
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-Port $server_port;
proxy_redirect off;
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
client_max_body_size 20m;
}
}
EOF

启用并重载:

Terminal window
ln -sf /etc/nginx/sites-available/<SERVICE_DOMAIN> /etc/nginx/sites-enabled/<SERVICE_DOMAIN>
nginx -t
systemctl reload nginx

七、验证结果h2

检查 HTTP 是否跳转 HTTPS:

Terminal window
curl -I http://<SERVICE_DOMAIN>

预期结果:

HTTP/1.1 301 Moved Permanently
Location: https://<SERVICE_DOMAIN>/

检查 HTTPS 是否正常:

Terminal window
curl -I https://<SERVICE_DOMAIN>

预期结果:

HTTP/2 200

或者:

HTTP/1.1 200 OK

检查后端服务:

Terminal window
curl -I http://127.0.0.1:<BACKEND_PORT>

八、常见错误h2

8.1 proxy_pass 报错h3

错误示例:

invalid number of arguments in "proxy_pass" directive

正确写法:

proxy_pass http://127.0.0.1:<BACKEND_PORT>;

proxy_pass 这一行后面不要加任何多余内容。

错误写法:

proxy_pass http://127.0.0.1:<BACKEND_PORT>; 其他文字

8.2 花括号错误h3

错误示例:

unexpected "}"

查看配置行号:

Terminal window
nl -ba /etc/nginx/sites-enabled/<SERVICE_DOMAIN>

检查每一个 { 是否都有对应的 }


8.3 HTTP 正常,HTTPS 到了其他站点h3

表现:

Terminal window
curl -I http://<SERVICE_DOMAIN>

正常返回后端服务。

但是:

Terminal window
curl -kI https://<SERVICE_DOMAIN>

返回了其他站点页面。

原因通常是该域名还没有独立的 443 配置,或者证书配置没有生效。

处理:

Terminal window
certbot --nginx -d <SERVICE_DOMAIN>
nginx -t
systemctl reload nginx

8.4 无痕模式正常,普通模式跳错h3

通常是浏览器缓存了旧的 301、HSTS 或站点数据。

处理方向:

清理该域名的站点数据
清理浏览器 DNS 缓存
清理 HSTS 记录
重新打开浏览器

Chrome / Edge 可检查:

chrome://settings/siteData
edge://settings/siteData

HSTS 页面:

chrome://net-internals/#hsts
edge://net-internals/#hsts

九、性能测试h2

测试后端响应:

Terminal window
curl -o /dev/null -s -w "DNS:%{time_namelookup}s Connect:%{time_connect}s TTFB:%{time_starttransfer}s Total:%{time_total}s\n" http://127.0.0.1:<BACKEND_PORT>

测试 HTTPS 响应:

Terminal window
curl -o /dev/null -s -w "DNS:%{time_namelookup}s Connect:%{time_connect}s TLS:%{time_appconnect}s TTFB:%{time_starttransfer}s Total:%{time_total}s\n" https://<SERVICE_DOMAIN>

如果服务器本机测试很快,但浏览器访问感觉慢,通常是客户端到服务器线路、浏览器缓存或页面资源加载问题。


十、FRP 场景h2

如果后端服务是通过 FRP 穿透到服务器本机端口:

服务器本机端口:<FRP_REMOTE_PORT>
内网服务端口:<LOCAL_SERVICE_PORT>

Nginx 中直接反代:

proxy_pass http://127.0.0.1:<FRP_REMOTE_PORT>;

FRPC 示例:

serverAddr = "<VPS_PUBLIC_IP>"
serverPort = 7000
auth.method = "token"
auth.token = "<FRP_TOKEN>"
transport.tcpMux = true
transport.tcpMuxKeepaliveInterval = 30
[[proxies]]
name = "<PROXY_NAME>"
type = "tcp"
localIP = "127.0.0.1"
localPort = <LOCAL_SERVICE_PORT>
remotePort = <FRP_REMOTE_PORT>
transport.useCompression = true

重启 frpc:

Terminal window
systemctl restart frpc

服务器检查端口:

Terminal window
ss -lntp | grep <FRP_REMOTE_PORT>
curl -I http://127.0.0.1:<FRP_REMOTE_PORT>

十一、日志和维护h2

查看访问日志:

Terminal window
tail -f /var/log/nginx/<SERVICE_DOMAIN>.access.log

查看错误日志:

Terminal window
tail -f /var/log/nginx/<SERVICE_DOMAIN>.error.log

检查 Nginx 配置:

Terminal window
nginx -t

重载 Nginx:

Terminal window
systemctl reload nginx

检查证书续期:

Terminal window
certbot renew --dry-run