Ubuntu 20.04 Nginx生产部署:ufw、systemctl与nginx.conf协同配置指南
1. 项目概述:为什么在 Ubuntu 20.04 上装 Nginx 不是“点几下就完事”的事
Nginx 是我过去十年里部署频率最高的服务之一——从给客户搭静态官网、反向代理后端 API,到给嵌入式设备做轻量级 Web 控制台,再到给边缘计算节点做 TLS 终结和请求路由,它几乎是我工具箱里最常被掏出的那把瑞士军刀。但很多人第一次在 Ubuntu 20.04 上敲sudo apt install nginx的时候,以为这就完成了;结果打开浏览器看到 “Welcome to nginx!” 页面,就关掉终端去写周报了。这就像买了辆保时捷却只让它在停车场原地怠速——你没动过油门,也没调过悬挂,更不知道它的差速锁在哪。
Ubuntu 20.04 是一个长期支持(LTS)版本,内核稳定、软件源成熟,但它默认安装的 Nginx 版本是1.18.0(截至 2024 年底仍为官方仓库主版本),这个版本发布于 2020 年 4 月,距今已超四年。它不支持 HTTP/3(QUIC)、不内置对 OpenSSL 3.0 的完整兼容、缺少proxy_http_version 1.1在某些 upstream 场景下的自动降级逻辑优化,更重要的是——它没有集成ngx_http_geoip2_module这类现代地理围栏必需的模块。而这些,恰恰是真实业务中绕不开的硬需求:比如你要做灰度发布,靠 IP 段区分用户群;比如你要防爬虫,得根据 ASN 判定请求来源是否异常;比如你要给海外用户提供低延迟访问,必须启用 HTTP/3 + Brotli 压缩组合。
更关键的是,Ubuntu 20.04 默认启用了ufw(Uncomplicated Firewall),但它对 Nginx 的规则管理极其“不智能”:sudo ufw allow 'Nginx Full'看似省事,实则会同时放行 80 和 443 端口,哪怕你当前只跑 HTTP 服务;而一旦你后续配置了 HTTPS,又忘了删掉 80 端口的规则,就等于主动暴露了一个未加密的入口,给中间人攻击留了活口。这不是理论风险——我在三个不同客户的生产环境里都复现过这类配置漂移导致的证书链校验失败问题,根源全在 ufw 规则与 Nginx 实际监听状态不同步。
还有systemctl——它不是chkconfig的翻版,也不是 Windows 服务管理器的 Linux 马甲。sudo systemctl edit nginx这个命令,表面看只是编辑服务单元,实则牵扯到覆盖片段(drop-in)机制、依赖图重载、启动顺序仲裁等一整套 systemd 内部逻辑。我见过太多人用nano直接改/lib/systemd/system/nginx.service,结果系统升级后被包管理器覆盖,服务突然无法 reload;也有人在ExecStartPre里写了sleep 2试图“等网络就绪”,却不知 systemd 有After=network-online.target这种语义化依赖,根本不需要手写延时。
至于nginx.conf,它从来就不是一份“拿来即用”的模板。Ubuntu 官方包自带的原始配置文件(位于/etc/nginx/nginx.conf)只有 62 行,其中 37 行是注释,真正生效的指令不到 25 条。它连最基本的client_max_body_size 100M;都没设,意味着你上传一个 10MB 的图片就会直接返回 413;它默认关闭gzip_vary on;,导致 CDN 缓存时无法区分压缩/未压缩版本;它把include /etc/nginx/sites-enabled/*;放在 http 块末尾,看似合理,实则一旦你在 sites-enabled 里写了server { listen 80; }却忘了加default_server,Nginx 就会把所有未匹配域名的请求全部打给第一个加载的 server 块——而这个“第一个”取决于文件系统排序,不是你写的顺序。
所以,这篇内容不是教你怎么“安装 Nginx”,而是带你亲手拆开 Ubuntu 20.04 这台车的引擎盖,看清 Nginx、ufw、systemctl、nginx.conf 四者之间真实的咬合关系。你会知道:什么时候该用apt而不是源码编译;为什么ufw allow 443比ufw allow 'Nginx Full'更安全;systemctl daemon-reload和systemctl restart nginx的执行顺序为什么不能颠倒;以及如何从原始nginx.conf出发,一行一行补全生产环境必需的 17 项核心参数。这不是教程,是我在 237 次 Nginx 部署中踩出来的路径标记。
2. 核心设计思路:为什么选择 APT 安装而非源码编译?四个不可妥协的前提
在 Ubuntu 20.04 上部署 Nginx,第一步永远不是敲命令,而是回答一个问题:这次部署的核心约束是什么?我把过去所有项目归为四类典型场景,每种场景对应一套完全不同的技术选型逻辑:
2.1 场景一:内部管理系统(Intranet Admin Panel)
典型特征:服务只对局域网开放,无公网 IP,不涉及 HTTPS,前端是 Vue/React 打包后的静态资源,后端是 Python Flask 或 Node.js 提供的 REST API。这种场景下,我坚持用apt install nginx,理由非常具体:
- Ubuntu 官方仓库的 Nginx 1.18.0 已通过 Canonical 的 LTS 安全审计,所有 CVE 补丁(如 CVE-2021-23017、CVE-2022-41741)都会随
sudo apt upgrade自动推送,无需人工干预; apt安装的二进制文件路径、日志位置、配置目录全部遵循 FHS(Filesystem Hierarchy Standard)标准,/var/log/nginx/、/etc/nginx/sites-available/这些路径被 Ansible、SaltStack 等自动化工具深度适配,改用源码安装会导致整个运维流水线断裂;- 最关键的是:
apt安装的 Nginx 启动脚本(/usr/sbin/nginx)内置了-t参数的自动语法检查逻辑,每次systemctl reload nginx前都会强制执行nginx -t,而源码编译的二进制默认不带此行为,必须手动在 systemd unit 里加ExecReload=/usr/local/nginx/sbin/nginx -s reload+ExecStartPre=/usr/local/nginx/sbin/nginx -t,稍有疏忽就可能 reload 一个语法错误的配置,导致服务中断。
提示:如果你的内部系统需要 WebSocket 支持(比如用 Nginx 反向代理 Socket.IO),请务必在
location /socket.io/块中添加三行:proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";这不是可选项——这是 WebSocket 协议握手的强制要求。漏掉任何一行,客户端连接都会卡在
pending状态。
2.2 场景二:对外提供 HTTPS 的 SaaS 服务(Public SaaS)
典型特征:有独立域名,需 Let's Encrypt 免费证书,要支持 HTTP/2、OCSP Stapling、HSTS,且未来可能接入 WAF 或 CDN。这种场景下,我依然用apt,但会额外启用 Nginx 官方提供的mainline 仓库(非 stable)。原因很现实:
- Ubuntu 20.04 的
nginx-full包虽然包含大多数模块(http_ssl_module,http_gzip_module,http_realip_module),但它不包含http_v2_module的完整实现——准确地说,它支持 HTTP/2,但不支持 ALPN 协商中的h2字符串优先级控制,这会导致某些旧版 Android WebView 无法建立 HTTP/2 连接; - mainline 仓库(
http://nginx.org/packages/mainline/ubuntu/)提供的 Nginx 1.25.x 版本,不仅完整支持 ALPN,还修复了ssl_buffer_size在高并发下内存泄漏的问题(CVE-2023-37021),而这个问题在 1.18.0 中依然存在; - mainline 仓库的
.deb包与 Ubuntu 系统深度集成:dpkg -i安装后,systemctl管理方式完全不变,ufw规则语法也不变,唯一变化的是/usr/sbin/nginx -v输出的版本号——这意味着你无需修改任何 CI/CD 脚本、监控告警规则或文档,就能获得新特性。
操作上,只需三步:
# 1. 导入 Nginx 官方 GPG 密钥 curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg # 2. 添加 mainline 仓库(注意:focal 对应 Ubuntu 20.04) echo "deb [arch=amd64 signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/mainline/ubuntu/ focal nginx" | sudo tee /etc/apt/sources.list.d/nginx.list # 3. 更新并安装(会自动卸载旧版) sudo apt update && sudo apt install nginx注意:不要执行
sudo apt dist-upgrade,因为 mainline 仓库的nginx包版本号(如 1.25.3)高于 Ubuntu 官方仓库(1.18.0),dist-upgrade可能触发不必要的内核或库升级,破坏系统稳定性。只用apt install nginx即可精准替换。
2.3 场景三:需要定制模块的嵌入式设备(Embedded Device)
典型特征:ARM64 架构的工控机、树莓派集群、Jetson Nano 边缘节点,内存 ≤2GB,存储 ≤16GB,要求最小化体积、禁用所有非必要模块(如http_perl_module,http_xslt_module),且必须静态链接 OpenSSL 以避免运行时依赖冲突。这种场景下,必须源码编译,但绝不是网上那些“下载 tar.gz → ./configure → make → make install”的粗暴流程。
我的编译策略基于三个硬性原则:
- 交叉编译而非本地编译:在 x86_64 开发机上用
aarch64-linux-gnu-gcc编译,生成 ARM64 二进制,避免在资源受限设备上耗尽 swap 空间; - 模块按需启用:只启用
--with-http_ssl_module --with-http_v2_module --with-http_gzip_static_module --with-file-aio --with-threads这五个模块,禁用所有 Perl/XSLT/GeoIP 相关模块(它们会引入额外的动态库依赖); - OpenSSL 静态链接:下载 OpenSSL 3.0.12 源码,
./config no-shared --prefix=/opt/openssl编译安装,然后在 Nginx configure 中指定--with-openssl=/path/to/openssl/src --with-openssl-opt="no-shared"。
最终生成的 Nginx 二进制大小可控制在 1.2MB 以内(对比apt版本的 3.8MB),内存占用峰值降低 40%,且彻底摆脱对系统 OpenSSL 库的依赖——这对需要长期离线运行的工业设备至关重要。
2.4 场景四:合规审计驱动的金融系统(Compliance-Audited Finance System)
典型特征:受 PCI DSS、等保三级等规范约束,要求所有组件版本可追溯、配置项可审计、日志留存 ≥180 天、TLS 加密套件必须满足TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256强制要求。这种场景下,我采用APT + 配置即代码(Configuration as Code)混合模式:
- 用
apt安装 Nginx,确保基础二进制来自可信源; - 所有配置文件(
nginx.conf,sites-enabled/*.conf)全部由 Git 仓库托管,每次变更必须经 PR Review + 自动化测试(用nginx -t+curl -I http://localhost检查健康端点); - 关键安全参数(如
ssl_ciphers,ssl_prefer_server_ciphers,add_header Strict-Transport-Security)全部写死在配置中,禁用任何运行时环境变量注入; - 日志路径统一指向
/var/log/nginx/audit/,并通过logrotate配置每日切割、压缩、保留 180 天,且logrotate配置本身也纳入 Git 管理。
这套方案的价值在于:当审计员要求提供“Nginx 配置变更记录”时,你直接给他一个 Git commit history 链接;当他问“如何证明 TLS 配置符合 PCI DSS 要求”,你打开nginx.conf文件,第 87 行就是ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256';,旁边还有一行注释# PCI DSS 4.1 compliant cipher suite, validated 2024-03-15。
这四种场景没有优劣之分,只有适用与否。你的项目属于哪一类?答案决定了你接下来敲的每一个命令。
3. 核心细节解析:ufw、systemctl、nginx.conf 三者的协同陷阱与避坑指南
在 Ubuntu 20.04 上,Nginx 不是孤立运行的。它像一台精密仪器,ufw是它的物理防护罩,systemctl是它的中央控制器,nginx.conf是它的神经中枢。三者任何一个环节出错,整台机器就会失灵。我整理了过去三年中高频出现的 12 类协同故障,按发生概率排序,并给出根因分析和实操解法。
3.1 ufw 规则与 Nginx 监听状态不同步:最隐蔽的“服务正常但无法访问”问题
现象:sudo systemctl status nginx显示 active (running),sudo ss -tlnp | grep :443确认 Nginx 正在监听 443 端口,但外部 curl 返回Connection refused或timeout。
根因分析:ufw的规则是静态的,而 Nginx 的监听端口是动态的。当你执行sudo ufw allow 'Nginx Full',ufw 会创建两条规则:
200 ALLOW Anywhere 80 200 ALLOW Anywhere 443但如果 Nginx 配置中listen 443 ssl;被注释掉,或者ssl_certificate路径错误导致 Nginx 启动时跳过 443 监听,ufw 规则依然存在——防火墙开着,但服务没在监听,结果就是“端口开着,服务没在”。
实操解法:永远用端口粒度而非服务名粒度管理 ufw 规则。执行:
# 先删除所有 Nginx 相关规则 sudo ufw delete allow 'Nginx Full' sudo ufw delete allow 'Nginx HTTP' sudo ufw delete allow 'Nginx HTTPS' # 再根据 Nginx 实际监听端口添加 sudo ss -tlnp | grep nginx | awk '{print $5}' | cut -d':' -f2 | sort -u | while read port; do sudo ufw allow $port done这段脚本会实时读取 Nginx 进程监听的所有端口(包括 8080、8443 等自定义端口),并为每个端口添加精确规则。它比ufw allow 443多了一步动态发现,但比ufw allow 'Nginx Full'少了 90% 的误放行风险。
提示:如果你的 Nginx 配置了多个 SSL 证书(SNI),且监听同一个 443 端口,ufw 规则依然只需
allow 443——因为 SNI 是应用层协议,防火墙只管传输层端口。
3.2 systemctl reload 与 nginx -s reload 的本质区别:一次 reload 失败引发的雪崩
现象:修改sites-enabled/myapp.conf后执行sudo systemctl reload nginx,命令无报错,但新配置未生效;进一步执行sudo nginx -s reload报错nginx: [emerg] bind() to 0.0.0.0:443 failed (98: Address already in use)。
根因分析:systemctl reload nginx实际执行的是systemctl kill --signal=SIGUSR2 nginx,它会触发 Nginx 的“优雅重启”流程:主进程 fork 新工作进程,新进程加载新配置并尝试绑定端口,成功后通知旧进程退出。但如果新配置中listen 443 ssl;的ssl_certificate路径错误(比如证书文件被chmod 600锁定,而 Nginx worker 进程以www-data用户运行),新进程无法读取证书,绑定失败,但主进程不会退出,导致新旧进程共存,端口被双占。
而nginx -s reload是直接向主进程发送SIGUSR2,行为一致,但systemctl版本会在 reload 前自动执行nginx -t语法检查,nginx -s reload不会——这就是为什么后者报错更早。
实操解法:永远遵循三步 reload 法则:
sudo nginx -t:强制语法检查,确认配置无误;sudo systemctl reload nginx:触发优雅重启;sudo systemctl status nginx:检查输出中是否有Reloading nginx configuration成功日志,以及Active: active (running)状态。
如果第 1 步失败,立刻sudo journalctl -u nginx --since "1 hour ago" | grep -i error查看详细错误;如果第 2 步失败,执行sudo nginx -V 2>&1 | grep -o 'configure arguments.*'查看编译参数,确认--with-http_ssl_module是否启用。
3.3 nginx.conf 原始配置的致命缺陷:从 62 行到生产就绪的 17 项必改参数
Ubuntu 20.04 的原始/etc/nginx/nginx.conf是一个精简到极致的骨架,但它漏掉了生产环境必需的 17 个关键参数。我按重要性排序,逐条说明修改理由和实操值:
| 参数 | 原始值 | 推荐值 | 修改理由 | 实操验证方法 |
|---|---|---|---|---|
worker_processes | auto | 2(双核 CPU)或4(四核) | auto在容器或虚拟机中可能识别为 1 个 CPU,导致并发能力不足;固定值便于压测建模 | ab -n 1000 -c 200 http://localhost/对比 QPS |
worker_connections | 768 | 4096 | 默认值仅支持约 1500 并发连接(768×2),现代 Web 应用轻松突破此限 | `ss -s |
keepalive_timeout | 65 | 30 | 过长的 keepalive 会占用 worker 进程,导致新连接排队;30 秒足够浏览器复用连接 | curl -I http://localhost/查看Connection: keep-alive和Keep-Alive: timeout=30 |
client_max_body_size | 未设置 | 100M | 未设置时默认为 1MB,上传大文件直接 413 | curl -F "file=@large.zip" http://localhost/upload |
client_header_timeout | 60 | 10 | 防止慢速 HTTP 头攻击(Slowloris),10 秒足够正常客户端发送完整头 | timeout 5s curl -v http://localhost/模拟超时 |
client_body_timeout | 60 | 12 | 同上,针对请求体传输超时 | 同上 |
send_timeout | 60 | 10 | 防止响应发送卡住,10 秒足够传输 10MB 以内响应 | `dd if=/dev/zero bs=1M count=50 |
gzip | off | on | 启用 Gzip 压缩,减少带宽消耗 | curl -H "Accept-Encoding: gzip" -I http://localhost/查看Content-Encoding: gzip |
gzip_vary | off | on | 告诉 CDN 和代理缓存压缩/未压缩版本,避免缓存污染 | 同上,检查Vary: Accept-Encoding |
gzip_types | text/plain text/css application/json | text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript | 补全常见 MIME 类型,避免 JS/CSS 未压缩 | 同上,检查不同后缀文件的Content-Encoding |
ssl_protocols | TLSv1.2 TLSv1.3 | TLSv1.2 TLSv1.3(显式写出) | 防止未来 Nginx 版本默认启用 TLSv1.1 | openssl s_client -connect localhost:443 -tls1_1应失败 |
ssl_ciphers | HIGH:!aNULL:!MD5:!RC4:!kRSA | TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384 | 强制使用前向保密(PFS)和 AEAD 加密套件 | nmap --script ssl-enum-ciphers -p 443 localhost |
ssl_prefer_server_ciphers | off | on | 确保服务器选择最强 cipher,而非客户端推荐 | 同上 |
ssl_session_cache | shared:SSL:10m | shared:SSL:50m | 10m 仅支持约 4000 个会话,50m 支持 20000+ | openssl s_client -connect localhost:443 -reconnect查看Session-ID:是否复用 |
ssl_session_timeout | 5m | 4h | 延长会话复用时间,减少 TLS 握手开销 | 同上,观察多次 reconnect 的Session-ID是否相同 |
add_header X-Frame-Options | 未设置 | DENY | 防止点击劫持(Clickjacking) | curl -I http://localhost/查看响应头 |
add_header X-Content-Type-Options | 未设置 | nosniff | 防止 MIME 类型嗅探攻击 | 同上 |
修改方法:在/etc/nginx/nginx.conf的http { }块内,所有参数必须放在include /etc/nginx/sites-enabled/*;之前,否则会被子配置覆盖。例如:
http { # ... 其他参数 add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; include /etc/nginx/mime.types; default_type application/octet-stream; include /etc/nginx/sites-enabled/*; # 必须放在这里 }注意:
add_header指令在location块中会覆盖server或http块中的同名指令,而不是追加。所以全局安全头必须在http或server级别设置,不能放在location /api/里。
3.4 systemctl edit 的正确姿势:为什么 90% 的人用错了这个命令
sudo systemctl edit nginx是修改 Nginx 服务行为的高级功能,但它不是让你直接改/lib/systemd/system/nginx.service。它的底层机制是创建drop-in 片段(drop-in snippet),即在/etc/systemd/system/nginx.service.d/override.conf中写覆盖配置。
常见错误:
- 错误1:执行
sudo systemctl edit nginx后,在 nano 里直接写ExecStart=/usr/sbin/nginx -g "daemon off;"——这会覆盖整个ExecStart,导致systemctl start nginx失败,因为原始ExecStart包含-c /etc/nginx/nginx.conf参数,你删掉了; - 错误2:在
override.conf中写[Service]两次,导致语法错误; - 错误3:修改后忘记执行
sudo systemctl daemon-reload,导致修改不生效。
正确做法(以添加启动前健康检查为例):
# 1. 创建 drop-in 片段 sudo systemctl edit nginx # 2. 在打开的编辑器中输入(注意:只能有一个 [Service] 块) [Service] ExecStartPre=/bin/sh -c 'while ! curl -f http://localhost/healthz 2>/dev/null; do sleep 1; done' # 3. 保存退出后,必须重载 systemd 配置 sudo systemctl daemon-reload # 4. 验证是否生效 sudo systemctl cat nginx | grep -A5 "ExecStartPre"这个ExecStartPre的作用是:在 Nginx 主进程启动前,循环检查http://localhost/healthz端点(通常由后端服务提供),直到返回 HTTP 200 才继续。它比After=network-online.target更精准,因为网络通了不代表后端服务已就绪。
提示:
systemctl edit默认使用的编辑器是nano,如果你想改成vim,执行export EDITOR=vim再运行命令即可。但切记:sudo会重置环境变量,所以必须sudo EDITOR=vim systemctl edit nginx。
4. 实操全流程:从零开始部署一个 HTTPS 前端项目(含 Let's Encrypt 自动续期)
现在,我们把前面所有知识点串起来,完成一个真实场景:将一个 Vue CLI 打包的前端项目(dist/目录)部署到 Ubuntu 20.04,通过 Nginx 提供 HTTPS 访问,并自动申请和续期 Let's Encrypt 证书。整个过程不依赖 Docker,纯原生系统配置。
4.1 环境准备与基础安装
首先确认系统状态:
# 检查 Ubuntu 版本 lsb_release -a | grep "Release\|Codename" # 检查内核和架构(确保是 amd64 或 aarch64) uname -m # 更新系统(关键:修复已知安全漏洞) sudo apt update && sudo apt full-upgrade -y # 安装基础工具(ufw、curl、wget、unzip 必备) sudo apt install -y ufw curl wget unzip gnupg2 ca-certificates安装 Nginx(采用 mainline 仓库,获取最新特性):
# 导入 Nginx 官方密钥 curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg # 添加 mainline 仓库 echo "deb [arch=amd64 signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/mainline/ubuntu/ focal nginx" | sudo tee /etc/apt/sources.list.d/nginx.list # 安装(会自动卸载旧版) sudo apt update && sudo apt install -y nginx # 验证版本 sudo nginx -v # 应输出 nginx version: nginx/1.25.3 # 启动并设为开机自启 sudo systemctl enable nginx && sudo systemctl start nginx4.2 配置 ufw 防火墙:最小权限原则
# 重置 ufw(清除所有规则) sudo ufw reset # 设置默认策略:拒绝所有入站,允许所有出站 sudo ufw default deny incoming sudo ufw default allow outgoing # 只放行必要端口:SSH(22)、HTTPS(443)、HTTP(80,用于 Let's Encrypt 验证) sudo ufw allow 22 sudo ufw allow 443 sudo ufw allow 80 # 启用 ufw(注意:启用后 SSH 连接不会断,因为 22 端口已放行) sudo ufw --force enable # 查看规则 sudo ufw status verbose提示:
ufw allow 80是临时的,Let's Encrypt 验证完成后可以sudo ufw delete allow 80,但建议保留,因为很多监控探针(如 Prometheus blackbox exporter)会通过 HTTP 80 端口探测服务健康状态。
4.3 部署前端静态文件
假设你的前端项目打包后生成dist/目录,将其上传到服务器/var/www/myapp/:
# 创建网站根目录 sudo mkdir -p /var/www/myapp # 上传 dist 目录(示例:用 scp) # scp -r ./dist/* user@your-server:/var/www/myapp/ # 设置正确权限(Nginx 以 www-data 用户运行) sudo chown -R www-data:www-data /var/www/myapp sudo chmod -R 755 /var/www/myapp # 验证文件可读 sudo -u www-data ls -la /var/www/myapp/4.4 配置 Nginx Server Block:HTTPS + HTTP/2 + 安全头
创建/etc/nginx/sites-available/myapp:
# /etc/nginx/sites-available/myapp server { listen 80; server_name myapp.example.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name myapp.example.com; # SSL 证书(Let's Encrypt 自动生成) ssl_certificate /etc/letsencrypt/live/myapp.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/myapp.example.com/privkey.pem; # SSL 优化(来自前文 3.3 节) ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:50m; ssl_session_timeout 4h; ssl_session_tickets off; ssl_stapling on; ssl_stapling_verify on; # 安全头(来自前文 3.3 节) add_header X-Frame-Options DENY always; add_header X-Content-Type-Options nosniff always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Download-Options noopen always; add_header X-Permitted-Cross-Domain-Policies none always; add_header Referrer-Policy no-referrer-when-downgrade always; add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-src 'none'; object-src 'none'; base-uri 'self'; form-action 'self';" always; # 前端路由(Vue Router history 模式) root /var/www/myapp; index index.html; location / { try_files $uri $uri/ /index.html; } # API 反向代理(如果后端在 http://localhost:3000) # location /api/ { # proxy_pass http://localhost:3000/; # proxy_http_version 1.1; # proxy_set_header Upgrade $http_upgrade; # proxy_set_header Connection "upgrade"; # 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|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; } # 健康检查端点 location /healthz { return 200 "OK"; add_header Content-Type text/plain; } }启用配置:
# 创建符号链接 sudo ln -sf /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/myapp # 测试配置语法 sudo nginx -t # 重新加载 Nginx sudo systemctl reload nginx4.5 申请 Let's Encrypt 证书:Certbot + Nginx 插件
安装 Certbot:
sudo apt install -y certbot python3-certbot-nginx # 申请证书(自动修改 Nginx 配置,添加 443 监听和证书路径) sudo certbot --nginx -d myapp.example.com --non-interactive --