ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Nginx实现本地HTTP强制跳转HTTPS的完整配置指南

Nginx实现本地HTTP强制跳转HTTPS的完整配置指南 很多人第一次遇到“本地HTTP转HTTPS”这个需求多半不是闲着没事而是被现实逼的小程序或公众号后台的回调地址要求必须是HTTPS、浏览器里用到摄像头或地理位置需要安全上下文、联调第三支付接口时对方要求验签地址必须走HTTPS、又或者你只是想在外网被朋友访问一下本机服务先加一层SSL再说。Nginx在这类场景里几乎是标准答案它本身就是一个高性能的反向代理放着它就是劫持本机的HTTP端口流量在Nginx这层把TLS终结掉再转回后端的HTTP服务。整个过程不需要改业务代码所有HTTP流量统统强制跳到HTTPS后端甚至不知道外面发生了什么。这篇文章我会从零开始把Nginx实现本地HTTP强制转HTTPS的完整过程理顺包括自签名证书怎么生成、nginx.conf怎么配、坑在哪里以及局域网里手机访问时怎么让证书不报错。适合正在做本地开发、前后端联调、想给本地服务套一层HTTPS但不想花钱买证书的人也适合刚接触Nginx、对SSL配置还不太熟悉的读者照着抄基本能跑通。1. 内容整体设计与思路拆解1.1 为什么是Nginx而不是直接在业务代码里开HTTPS先说说方案选型。本地服务如果要支持HTTPS理论上可以直接在应用服务器里配置证书比如Spring Boot里塞一个tomcat证书、Node.js里用https模块启动。但这样做有很明显的问题每换一个项目就要重新配一遍有些框架配SSL挺折腾的还有的就是一些嵌入式HTTP库根本不支持HTTPS你总不能把底层库都改了吧。用Nginx做统一入口就好办多了。Nginx监听443端口负责处理TLS握手、证书下发、加解密然后它把解密后的普通HTTP请求转发给后端的本地服务。对后端来说它看到的还是HTTP请求不用做任何改动。这个模式就是标准的TLS终结Nginx在这个架构里扮演的是反向代理TLS网关的角色。另外Nginx做HTTPS还有一个隐藏好处证书、跳转逻辑、HTTP/2这些能力都集中在代理层统一管理以后想换证书、想调整跳转策略改配置文件reload一下就完事不需要重新编译或者重启业务进程。这也是很多线上架构把Nginx放在最前面统一处理SSL的原因本地开发其实只是把线上这套玩法缩小的版本。1.2 证书方案选型自签名还是公共CA签发HTTPS必须要证书但证书从哪来是个绕不开的问题。线上的网站一般用Lets Encrypt这类免费CA签发证书浏览器天然信任不用任何额外操作。但本地开发环境的域名通常是一个外部无法访问的地址比如localhost、127.0.0.1又或者是局域网里的192.168.x.xCA全球签发机构一般不会给纯IP或者非公网域名签证书就算签了也只能用几十天、验证也麻烦。所以本地场景最常用的是自签名证书用OpenSSL自己生成一个然后让Nginx加载。自签名证书的缺点是浏览器不信任访问时会有一个红色警告页点“高级-继续前往”就能进。对于本地调试来说这个警告完全可以接受因为证书加密本身是有效的只是没有权威CA给它背书而已。如果你实在不想要那个警告页后面我会提到mkcert这个工具它可以生成本地根证书并安装到系统信任列表里让浏览器完全信任自签名证书体验和正式证书几乎一样。但在那之前先把最基础的自签名方案跑通理解链路是怎么走的。2. 环境准备与证书生成实操2.1 不同平台下Nginx的安装方式Nginx的安装很简单不同操作系统各有各的装法我把自己用过的几种列出来供参考。Linux系Ubuntu/Debian用apt即可sudo apt update sudo apt install nginxCentOS/RHEL用yum或dnfsudo yum install nginxmacOS用户有Homebrew最方便brew install nginxWindows用户可以直接去Nginx官网下载Windows版本解压后执行nginx.exe就能跑起来但Windows下把它注册成服务稍微麻烦点建议优先用WSL或者虚拟机做测试。安装完成后建议先确认一下服务和版本nginx -v sudo systemctl status nginx把Nginx跑起来后浏览器访问http://localhost如果能出现Nginx欢迎页说明基础环境没问题。这里有一个小坑就是80端口被占用会导致Nginx启动失败排查的时候可以先执行sudo lsof -i:80看看是谁占着端口。2.2 用OpenSSL生成自签名证书的完整命令证书生成的工具就是OpenSSL几乎所有Linux和macOS都自带。没有的话可以用sudo apt install openssl或者brew install openssl补上。生成证书前我习惯建一个专门的目录收拾证书文件别堆在/etc/nginx下乱七八糟的sudo mkdir -p /etc/nginx/ssl cd /etc/nginx/ssl然后生成自签名证书和私钥核心命令如下sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout nginx-selfsigned.key \ -out nginx-selfsigned.crt \ -subj /CCN/STBeijing/LBeijing/ODevLocal/OUIT/CNlocalhost \ -addext subjectAltNameDNS:localhost,IP:127.0.0.1,IP:192.168.1.10这里每个参数都简单解释一下。-x509表示直接生成自签名证书而不是证书签名请求-nodes表示私钥不加密这样Nginx启动时不用我们手动输密码这个在自动化启动场景下很关键-days 365表示证书有效期一年-newkey rsa:2048生成一个2048位的RSA私钥强度足够日常开发用-subj是证书的主体信息里面的CNlocalhost会作为默认匹配的域名最后那个-addext是重中之重它给证书加了Subject Alternative Name扩展写明了这个证书同时也匹配localhost、127.0.0.1和你局域网的IP。为什么要特别强调SAN这个参数因为新版的Chrome和Firefox已经不再信任不含SAN扩展的证书如果你偷懒只写-subj不写-addext浏览器会直接提示证书链无效或者NET::ERR_CERT_COMMON_NAME_INVALID就算点继续都很难进去。这是一个相当容易踩的坑我最早做的时候就是少了这一步折腾了半小时才反应过来。生成完可以用下面命令看一眼证书内容openssl x509 -in nginx-selfsigned.crt -text -noout | grep -A 1 Subject Alternative Name能看到DNS:localhost, IP Address:127.0.0.1, IP Address:192.168.1.10这样的输出就说明证书的SAN已经加进去了。2.3 证书文件的安全权限设置这一步很多人会忽略但实际很重要。私钥文件一旦泄露相当于你HTTPS加密的大门钥匙被人复制了所以在生成完证书后我习惯把私钥权限收紧sudo chmod 600 nginx-selfsigned.key sudo chmod 644 nginx-selfsigned.crt私钥文件权限是600只有root能读写证书文件是644因为证书本身是公开的Web服务器进程需要读取它不能设太严。Nginx的worker进程通常以nginx用户运行但它启动时读配置文件用的是root权限所以636这种极端权限反而可能出问题。保持上述权限后启动阶段不会遇到权限拒绝的报错。3. 核心Nginx配置HTTP强制跳转HTTPS3.1 一份可用的完整配置模板安装和证书准备好以后重点就落在nginx的配置上了。Nginx的配置入口是nginx.conf如果嫌主配置文件太长可以在conf.d目录下新建一个文件例如local-https.conf然后在nginx.conf的http块里把该目录include进来。Debian系的Nginx默认就有/etc/nginx/sites-available和/etc/nginx/sites-enabled的方案我们用哪种都行本质是一样的。下面这份配置是我手写精简过的直接复制后改改后端端口就能用server { listen 80; listen [::]:80; server_name localhost 192.168.1.10; return 301 https://$host$request_uri; } server { listen 443 ssl; http2 on; server_name localhost 192.168.1.10; ssl_certificate /etc/nginx/ssl/nginx-selfsigned.crt; ssl_certificate_key /etc/nginx/ssl/nginx-selfsigned.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; 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; } }这里我假设后端的本地业务服务跑在8080端口比如一个Spring Boot服务、一个Node.js服务或者一个Flask应用。你只需要把proxy_pass里的端口改成你真正的服务端口就行。3.2 配置拆解80跳转块和443监听块各自做了什么上面这份配置其实就是两个server块一个是80端口的入口一个是443端口的入口。第一个server块监听80端口收到请求后执行return 301 https://$host$request_uri;意思就是告诉浏览器这个页面已经永久迁移到HTTPS地址了请直接去新的HTTPS链接。$host变量会自动替换成你请求里的域名或IP$request_uri会带上原始路径和查询参数所以从http://localhost:80/foo?bar1会被完整跳转到https://localhost/foo?bar1参数一个都不会丢。为什么不直接把80端口的请求转发到443端口里呢return 301是标准做法搜索引擎和客户端都能理解「这个网址以后都用HTTPS」。而且301跳转是永久性的浏览器会记住这个跳转下次直接访问HTTPS减小一次不必要的往返。第二个server块是核心。listen 443 ssl;告诉Nginx这个server块专门处理443端口的SSL连接http2 on;开启HTTP/2协议多路复用能明显提升本地请求并发时的体验ssl_certificate和ssl_certificate_key指向我们刚才生成的两个文件ssl_protocols TLSv1.2 TLSv1.3;表示只启用这两个较新且安全的TLS版本老旧的TLSv1.0和TLSv1.1有已知漏洞本地开发也没必要兼容古董客户端。location /块里的proxy_pass http://127.0.0.1:8080;才是真正的反向代理转发它把443端口收到的HTTPS请求解密之后再以HTTP协议转发给本机的8080端口。后面几行proxy_set_header非常重要它们把原始请求的信息传递给后端特别是X-Forwarded-Proto $scheme这个头告诉后端请求原来是HTTPS协议进来的后端如果要做协议相关的逻辑判断比如生成回调地址、重定向地址就需要看这个头才知道该返回HTTPS链接而不是HTTP链接。后面这几个头也是同样的道理。3.3 校验配置并加载生效配置改完之后千万不要直接重启Nginx先跑一遍语法检查sudo nginx -t如果输出nginx: configuration file /etc/nginx/nginx.conf test is successful说明配置格式没问题。接着执行sudo systemctl reload nginxreload是平滑重载它不会中断正在处理的请求只是让Nginx重新读取配置。如果改坏了或者端口被占用reload时会报错并且保持旧配置继续运行这是个比较安全的机制。3.4 局域网内其他设备访问时的配置要点本地环境跑通之后很多人接下来就想让同一局域网下的手机或者另一个电脑访问。这一步除了Nginx配置外还有几个容易忽略的细节。首先Nginx如果监听的是listen 443 ssl;默认会同时绑定0.0.0.0:443也就是所有网卡接口理论上局域网设备可以访问。但如果你的配置文件里写的是listen 127.0.0.1:443 ssl;那就只有本机能访问了这个问题出现过不少次。其次是系统防火墙Ubuntu的ufw、CentOS的firewalld都可能在默默拦着端口。放行443sudo ufw allow 443 # 或者 sudo firewall-cmd --permanent --add-port443/tcp sudo firewall-cmd --reload最后是证书里的SAN要包含你访问时用的IP这点在生成证书那一步已经加入了IP:192.168.1.10所以局域网设备访问https://192.168.1.10时证书匹配上只有一个不受信任的警告不会有域名不匹配的额外报错。如果手机连不上先用电脑在同一个WiFi下ping一下这个IP再把后端服务的host从127.0.0.1改成0.0.0.0因为有些开发服务器默认只监听localhost。4. 启动验证与常见问题排查实录4.1 验证跳转是否生效的几个命令配置好了验证环节要系统一点别光靠浏览器看一下就完事。我一般会按顺序跑这几条命令。第一条验证80端口能否按预期跳转curl -I http://localhost如果配置正确返回的HTTP状态码应该是301 Moved Permanently并且响应头里的Location字段是https://localhost/这就说明HTTP到HTTPS的强制跳转已经在工作了。第二条验证443端口的TLS握手是否正常curl -k https://localhost这里的-k表示忽略证书不受信任的报错如果后端服务正常你会看到后端应用返回的内容。如果看到curl: (60) SSL certificate problem之类的内容别慌这正是自签名证书没有被curl信任的正常表现用-k绕过即可。第三条用OpenSSL工具检查证书链openssl s_client -connect localhost:443 -servername localhost这条命令会输出一大段TLS握手信息包括证书序列号、签发者、加密套件、协议版本等你可以从中确认证书是否被正常加载、TLS版本是否匹配。浏览器端验证更直观。访问http://localhost会立刻跳转到https://localhost出现警告页后依次点“高级-继续前往”就能看到后端服务的页面。若一切正常地址栏左侧有一个小锁图标但带横线表示证书不受信任但连接是加密的这个状态在本地开发场景下是完全可用的。4.2 常见报错现象与解决办法速查表在实际操作中各步骤的报错五花八门我把这几年见过的高频错误和排查思路整理成了表格按现象查原因最省事。现象可能原因解决办法nginx -t报权限错误私钥文件权限过于开放执行chmod 600 /etc/nginx/ssl/nginx-selfsigned.keynginx -t报端口被占用80或443端口被其他服务占用sudo lsof -i:443查看占用进程并处理浏览器访问直接拒绝连接Nginx没有启动或监听地址不对检查systemctl status nginx确认listen 443 ssl;没有绑定localhost警告页点“继续”后显示证书名称不匹配证书SAN里缺少对应的域名或IP重新生成证书并在-addext中补上IP或域名跳转到HTTPS后页面样式全乱页面里有HTTP资源被浏览器拦截全局搜索代码里的http://改成https://或直接用相对路径手机访问不通防火墙拦截或后端服务只监听127.0.0.1放行443端口后端启动参数设置host为0.0.0.0微信小程序/公众号回调失败小程序不信任自签名证书改用有公网域名正式CA证书的方案或用内网穿透跳转后变成无限循环重定向后端自己也加了HTTPS跳转逻辑把后端服务改为HTTP模式HTTPS由Nginx统一处理WebSocket连接失败Nginx没有转发Upgrade头在location中额外配置Upgrade和Connection头4.3 进阶方案用mkcert让浏览器完全信任自签名证书如果你实在受不了浏览器每次弹出的警告页这里有一个很成熟的本地开发方案mkcert。它的原理是生成本地根证书然后把这根证书安装到系统的信任列表里之后mkcert签出来的自签名证书就会被浏览器当作合法证书不再有警告。安装和使用都很简洁brew install mkcert # macOS # 或者 sudo apt install libnss3-tools mkcert -install mkdir -p /etc/nginx/ssl cd /etc/nginx/ssl mkcert localhost 127.0.0.1 192.168.1.10执行完后当前目录会出现localhost2.pem和localhost2-key.pem两个文件把nginx配置里的ssl_certificate和ssl_certificate_key指向它们reload一下Nginx再用浏览器访问地址栏就是一把正常的小锁了这在调试一些对安全上下文有严格要求的浏览器API时非常有用。需要注意mkcert的根证书装进了系统信任库只影响本机信任链局域网里的其他设备如果不安装这个根证书依然会看到警告。所以在多设备联调时要么每台设备都装一次根证书要么就用上一节里的-k方案忍受警告。4.4 补充场景Nginx反向代理WebSocket的配置本地开发时如果后端服务里有WebSocket接口比如一些热更新工具、在线协作应用简单拷贝上面的location配置会发现WebSocket握手总是失败。原因在于WebSocket协议升级需要传Upgrade和Connection两个请求头Nginx默认不会透传需要在location块里显式声明location /ws/ { proxy_pass http://127.0.0.1:8080; 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_http_version 1.1和那两行头信息。HTTP/1.0不支持WebSocketNginx默认使用HTTP/1.0转发所以必须显式设为1.1。如果你要转发的服务路径不止/ws/一个可以把WebSocket单独放在一个location其他HTTP请求走普通的location两个块互不干扰。这种情况下前端代码连接WebSocket时也要用wss://localhost/ws/xxx而不是ws://localhost/ws/xxx因为外面整条链路已经HTTPS了明文WebSocket会被浏览器拦截。这是一个特别容易漏的细节前端控制台会报Mixed Content: The page at https://... was loaded over HTTPS, but attempted to connect to an insecure WebSocket endpoint排查半天才发现是ws和wss用错了。4.5 给本地HTTPS环境预留的排错小技巧如果页面打开后还是各种不对最后分享一个通用的排查思路。先在浏览器开发者工具的Network面板里看请求是不是全部走的https被浏览器拦掉的http资源会直接显示为blocked再在控制台看有没有混合内容提示。然后去Nginx的访问日志和错误日志里找线索sudo tail -f /var/log/nginx/access.log sudo tail -f /var/log/nginx/error.log错误日志里常见的SSL_do_handshake() failed大多是因为客户端用了老旧的协议不是配置问题upstream timed out说明Nginx连不上后端端口先确认后端服务有没有起来、监听的是不是127.0.0.1:8080。如果日志全部正常但页面还是不对用curl -k -v https://localhost -H Host: localhost打印完整请求过程一般能定位到是TLS阶段的问题还是后端响应的问题。这整个方案我在本地联调中用过很多回最大的感受是Nginx做HTTP转HTTPS的成本极低真正花时间的全是证书信任和细节转发这一类的小问题。只要把证书文件和location头信息这两块搞明白后面换端口、换域名都只是改个参数的事。如果你在配置过程中遇到什么奇怪的报错先对照上面的排查表一条条过大概率不用折腾太久。
返回列表