最近给树莓派折腾了一个Nginx服务,把之前写的一个小网站放了上去,想着出门之后也能用手机随时打开看看。本来以为装好Nginx就能直接访问,结果被现实教育了一下:家里宽带根本没有公网IP,外网设备压根找不到这台树莓派。这篇就记录下我踩完坑之后的完整方案:树莓派上部署Nginx,再借助一台云服务器建立一条远程转发通道,让没有公网IP的内网Web也能被外网正常访问。整个过程不需要改宽带、不需要额外付费软件,适合所有想在内网部署Web服务并远程访问的朋友。
1. 树莓派部署Nginx的整体思路与方案选型
1.1 树莓派当Web服务器,到底靠不靠谱
很多人觉得树莓派性能弱,不适合当服务器。但实际做个人网站、内网服务展示非常够用。树莓派整机功耗大概5W上下,24小时开机一个月电费也就几块钱,比买一台云服务器便宜太多。而且树莓派跑的是完整的Linux系统,安装Nginx、配置站点和普通服务器几乎没有区别。如果你只是放静态页面、个人博客、家庭控制面板,树莓派完全能扛住。
它也有明显短板:TF卡寿命和IO性能是瓶颈,并发高了磁盘IO会拖后腿,日志写太多也会加速SD卡老化。所以选题要谨慎,适合个人项目展示、家庭内部工具、智能家居控制台这种低流量场景,不适合做高并发商业站。清楚了边界,用起来就很舒服。
1.2 无公网IP环境下的几种远程访问思路
宽带运营商分配给家庭的IP基本都是私网IP,外网无法直接访问到你家设备。想让外网访问内网Web,常见思路有这几条:
- 申请公网IP:联系运营商要公网IPv4,有些地方可能给,有些地方直接拒绝,而且多数公网IP不固定,还要配合DDNS动态解析,折腾下来周期很长。
- 路由器端口映射:在路由器上把外网端口映射到树莓派内网IP,这条路同样依赖公网IP,没有公网IP时根本玩不转。
- 中转通道:树莓派主动连接一台有公网IP的服务器,让服务器把收到的外网请求转给树莓派。这条路不要求树莓派有公网IP,是目前最通用的做法。
- 虚拟组网:树莓派和手机都装同一个组网客户端,组成虚拟局域网,通过虚拟IP直接访问,适合只有自己用、且愿意在每个设备上都装客户端的情况。
我实际选的是中转通道里的SSH远程转发。原因很简单:树莓派系统自带SSH,云服务器也自带SSH,不需要额外安装任何客户端工具,一条命令行就能打通。
1.3 我最终选择的架构
整条链路是:手机/电脑浏览器 -> 云服务器公网IP:8080 -> SSH远程转发通道 -> 树莓派Nginx:80 -> Web页面。
树莓派上的Nginx只在内网提供服务,云服务器相当于一个“门卫”,负责接收外网请求,再原封不动交给树莓派。由于通道是树莓派主动建立的,即使树莓派没有公网IP也不影响通信。访问者看到的是云服务器的IP和端口,背后真正响应的是树莓派上的Nginx。
这个架构的好处很直白:
- 树莓派不需要暴露任何入站端口,只需要能出网(能连到云服务器);
- 云服务器只需要监听一个端口,不需要部署复杂业务;
- 断线后可以自动重连,下面我会用Autossh实现;
- SSH本身加密,链路上的数据不会被明文读取。
2. 树莓派环境准备与Nginx安装避坑指南
2.1 先把系统基础收拾干净
建议直接刷Raspberry Pi OS Lite(无桌面版),把系统资源全留给服务。烧录到TF卡后,在boot分区创建一个空的ssh文件用于开启SSH;如果需要在无显示器场景下连接WiFi,再写一个wpa_supplicant.conf放进去。开机后用MobaXterm或系统自带终端SSH连接树莓派。
登录后先做几件事:
sudo apt update && sudo apt upgrade -y sudo raspi-config在raspi-config里确认文件系统已扩展、设置时区和主机名。新版系统默认自动扩展,但手动确认一下没坏处。如果下载速度慢,可以换成你所在网络访问较快的软件源,这一步能省大量时间。
顺手把系统时间和时区校准,否则后面看日志时时间对不上,排查问题容易懵。
2.2 安装Nginx并确认它在正常跑
安装命令非常简单:
sudo apt install nginx -y装好后查看运行状态:
sudo systemctl status nginx sudo systemctl enable --now nginx nginx -v默认情况下,Nginx站点根目录是/var/www/html,访问http://树莓派IP/就能看到欢迎页。先用浏览器或curl确认一下:
curl -I http://localhost如果返回HTTP/1.1 200 OK,说明Nginx已经正常监听80端口了。这里最容易踩的坑是80端口被其他服务占用,比如之前装过Apache,或者系统里有什么程序占用了端口。排查命令:
sudo lsof -i :80找到占用进程后处理掉,再启动Nginx。
2.3 把Web站点放到Nginx里
以我的站点为例,目录规划如下:
- 网站文件放在/var/www/myweb/
- 站点日志放在/var/log/nginx/myweb/
先创建目录并放入最简单的index.html:
sudo mkdir -p /var/www/myweb echo '<html><head><title>Pi Site</title></head><body><h1>Hello from Raspberry Pi</h1></body></html>' | sudo tee /var/www/myweb/index.html然后新建站点配置文件/etc/nginx/sites-available/myweb:
server { listen 80; server_name _; # 本机暂时没有域名,先用通配 root /var/www/myweb; index index.html; access_log /var/log/nginx/myweb_access.log; error_log /var/log/nginx/myweb_error.log; }再创建软链接启用站点:
sudo ln -s /etc/nginx/sites-available/myweb /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx这里有几个关键点:
- 多个站点时,建议用sites-available和sites-enabled这套管理方式,方便启停;
- 注意目录权限,Nginx以www-data用户运行,需要让www-data对网站目录有读取权限。我之前遇到403,十有八九是权限问题,改成
sudo chown -R www-data:www-data /var/www/myweb解决; - 写配置要时刻记得
nginx -t检查语法,直接reload容易挂掉正在跑的服务。
最后再验证一下本地访问:
curl -I http://127.0.0.1到这里,树莓派上的Web服务就基本跑起来了。
3. 通过SSH远程转发实现无公网IP远程访问
3.1 远程转发到底在转发什么
很多人一听到远程访问内网,第一反应是端口映射或者内网穿透工具。SSH远程转发其实也是一种内网穿透,只是把工具换成了我们最常用的SSH。
原理可以这样理解:树莓派和云服务器之间建立一条SSH连接,树莓派在这条连接上告诉云服务器:“请你帮我盯住公网的某个端口,只要有人来访问,把数据通过这条连接送给我,我来处理。” 于是手机访问云服务器IP:8080时,请求会被“塞进”那条SSH通道,一路送到树莓派的Nginx。SSH自带加密,整条链路私密性有保障,也不用担心中间人偷看内容。
这个方式最大的优势是树莓派主动发起外呼连接,家庭路由器一般不会拦截出站SSH,所以即使没有公网IP也能顺利建立连接。而且普通浏览器就能直接访问,不需要在手机或电脑上安装额外客户端。
3.2 在云服务器上做最小准备
首先需要一台有公网IP的云服务器,Linux系统即可,配置不用太高,1核1G完全够用。它平时只做流量转发,基本不消耗CPU。
在云服务器控制台的安全组里放行我们要用的端口,比如8080。然后在服务器上创建一个专门用来转发的用户,比如tunnel:
sudo useradd -m -s /bin/bash tunnel树莓派需要能免密登录到这个用户,所以要把树莓派的公钥放到云服务器上。在树莓派上执行:
ssh-keygen -t ed25519一路回车生成密钥对,然后把公钥内容追加到云服务器/home/tunnel/.ssh/authorized_keys里。具体操作是先把公钥打印出来:
cat ~/.ssh/id_ed25519.pub复制这段内容,在云服务器上:
sudo mkdir -p /home/tunnel/.ssh sudo sh -c 'echo "你复制的公钥内容" >> /home/tunnel/.ssh/authorized_keys' sudo chown -R tunnel:tunnel /home/tunnel/.ssh sudo chmod 700 /home/tunnel/.ssh sudo chmod 600 /home/tunnel/.ssh/authorized_keys这样树莓派就能免密登录到tunnel用户了。为了安全,可以给tunnel用户设置一个强密码,或者直接在sshd_config里禁用密码登录。
3.3 在树莓派上建立第一条通道
先在树莓派手动测试一下:
ssh -R 8080:localhost:80 tunnel@云服务器IP执行后SSH会保持在前台,窗口不要关。然后打开手机流量,访问http://云服务器IP:8080,如果能看到树莓派上的网页,说明通道已经打通了。
参数含义:
-R表示远程转发;8080:localhost:80表示把云服务器的8080端口和树莓派的80端口对接;tunnel@云服务器IP是登录云服务器的账户和地址。
注意,这里有个容易出问题的细节:SSH远程转发默认只绑定云服务器的127.0.0.1,外网访问不到。这时需要修改云服务器的sshd_config,添加一行:
GatewayPorts yes然后重启sshd服务。这样才能让转发端口绑定到云服务器的公网网卡上,外网才能访问。这一步非常关键,很多人通道看起来建好了,外网却死活连不上,就是卡在这里。
提示:GatewayPorts打开后,端口会暴露在公网上,务必在下一章里设置访问认证,不要裸奔。
3.4 让通道始终保持在线
手动的SSH连接一旦断开,通道就没了。解决方法是用autossh保持会话并自动重连。
在树莓派安装autossh:
sudo apt install autossh -y然后创建一个systemd服务,让它开机自启:
sudo nano /etc/systemd/system/web-tunnel.service内容如下:
[Unit] Description=SSH Remote Forward Tunnel After=network-online.target Wants=network-online.target [Service] User=pi ExecStart=/usr/bin/autossh -M 0 -N -R 8080:localhost:80 tunnel@云服务器IP ExecStop=/usr/bin/kill $MAINPID Restart=always RestartSec=5 Environment="AUTOSSH_GATETIME=0" [Install] WantedBy=multi-user.target参数解析:
-M 0表示关闭autossh自带的监测端口,和systemd配合时不需要它;Restart=always让服务挂掉后自动拉起;AUTOSSH_GATETIME=0让autossh在启动后立刻尝试连接,而不是傻等一段时间。
配置好后:
sudo systemctl daemon-reload sudo systemctl enable --now web-tunnel sudo systemctl status web-tunnel如果状态显示active (running),通道就已经在后台稳定维护了。树莓派重启后,通道也会自动建立,无需手动干预。
3.5 访问体验上的几个细节
访问地址是云服务器IP:8080,如果想去掉端口号、走标准80端口,可以在云服务器安全组放行80,然后把转发参数改成-R 80:localhost:80。不过公网80端口在很多云平台上有特殊管理策略,我的建议是先用一个高位端口(8080、8081)跑通,后续有域名再折腾。
如果你手头有域名,可以解析到云服务器IP,然后在云服务器上再跑一个入口服务,把443端口来的请求交到8080,这样访问时连端口都不用带,还能上HTTPS。这部分涉及到证书配置,放到后面的安全优化章节一起说。
4. 远程访问场景下的安全加固与优化
4.1 给Nginx加上访问认证
因为是个人站点,不希望随便谁扫到端口都能打开。最简单的办法是给Nginx加上用户名密码认证。
先生成密码文件:
sudo apt install apache2-utils -y sudo htpasswd -c /etc/nginx/.htpasswd webuser这个命令会提示输入两次密码,生成一个包含用户名和加密密码的文件。然后在站点配置里加上认证:
server { listen 80; server_name _; root /var/www/myweb; index index.html; auth_basic "Restricted Area"; auth_basic_user_file /etc/nginx/.htpasswd; access_log /var/log/nginx/myweb_access.log; error_log /var/log/nginx/myweb_error.log; }重新加载Nginx:
sudo nginx -t && sudo systemctl reload nginx再次访问时,浏览器会弹出一个登录框,输入webuser和密码才能看到内容。认证是在Nginx这一层完成的,即使有人扫到云服务器的8080端口,也会被登录框挡住,不会直接看到网站。
4.2 如何引入HTTPS加密
HTTP是明文传输,在公共WiFi下别人可以抓包看到页面内容。理想情况是启用HTTPS,有两个方向:
第一种,如果你有自己的域名,把域名解析到云服务器IP,然后在Nginx里申请Let's Encrypt证书。具体流程是用acme.sh之类的工具签发证书,然后在云服务器的入口层配置443监听,把解密后的请求交给转发端口。这个方案需要云服务器上有一个能处理HTTPS的服务,我只实验到“能访问”阶段,证书自动化续期还涉及一些权限配置,改动量比想象中多。
第二种,如果只是自己临时用,可以在树莓派上生成自签名证书,Nginx监听443,然后通过ssh -R 443:localhost:443做远程转发。这样访问时浏览器会提示“不安全”,但链路内容是加密的,比裸HTTP好很多。自签名证书生成命令:
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout /etc/nginx/ssl/myweb.key \ -out /etc/nginx/ssl/myweb.crt \ -subj "/CN=myweb.local"然后修改Nginx配置监听443并加载证书。我这里目前用的是HTTP+Basic Auth的组合,一来访问量小,二来密码认证已经挡住了大部分流量,只要我不在极不安全的WiFi下访问,问题不大。对隐私要求高的,强烈建议上HTTPS。
4.3 Nginx性能与日志优化
树莓派的TF卡读写能力有限,频繁写日志会拖慢性能并缩短卡寿命。所以要做几件事:
- 关闭或降低访问日志频率;
- 打开gzip压缩,减少传输体积;
- 设置静态资源缓存;
- 控制连接超时。
可以在站点配置里加这些内容:
gzip on; gzip_min_length 1k; gzip_types text/plain text/css application/json application/javascript; location ~* \.(jpg|jpeg|png|webp|gif|ico|css|js)$ { expires 7d; add_header Cache-Control "public"; }如果日志占用IO严重,可以干脆关掉access_log:
access_log off;或者把日志路径指向内存文件系统/dev/shm,重启后日志清空,适合纯粹为了排查临时开启。长期保存日志还是放外置USB硬盘或者远程日志服务更合理。
5. 常见问题排查与实战技巧
5.1 高频问题速查表
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 访问云服务器IP:8080超时 | 安全组未放行8080、服务器防火墙拦截、SSH通道断开 | 先在云服务器本地curl localhost:8080,再检查安全组 |
| Nginx启动失败 | 80端口被占用、配置文件语法错误 | 查看systemctl status nginx日志,用nginx -t检查语法 |
| 页面403 | 网站目录权限不足 | 确保www-data可读,使用chown -R www-data:www-data |
| 页面404 | root路径配置错误 | 检查站点根目录是否真实存在,文件是否在这个目录下 |
| 外网能访问但网页资源加载慢 | 未开启gzip、图片体积大、SSH链路质量差 | 开gzip,用压缩图片,观察链路延迟 |
| 通道反复断开 | SSH连接被路由器切断、云服务器断开空闲连接 | 用autossh自动重连,设置Restart=always |
5.2 一条清晰的排查思路
遇到问题先分清楚是内网问题还是外网问题。一层层排除:
第一步,在树莓派本地执行curl -I http://localhost,如果正常,说明Nginx没有问题。
第二步,在云服务器上执行curl -I http://localhost:8080,如果正常,说明SSH转发通道是通的。
第三步,从外网另一台设备访问http://云服务器IP:8080,如果不通,问题就在云服务器的安全组、防火墙,或者SSH的GatewayPorts没有正确开启。
这个方法能把故障范围快速缩小到某一层,不会盲目乱试。我几次排查下来,最终发现都是琐碎细节:要么安全组没放行,要么忘了加GatewayPorts,要么Nginx配置里某个分号写成了全角字符。
5.3 我的几条实操心得
第一,SD卡的IO真的经不起折腾。跑了一个月后我用iostat看负载,发现access_log是最大写入源。后来直接关掉访问日志,只在调试时临时打开,TF卡的压力降了一个量级。
第二,SSH密钥生成时不要设置口令,否则autossh在开机自启时会卡在密码输入那里,导致通道建不起来。想要安全,可以在云服务器端限制tunnel用户只能做远程转发,不能登录shell,具体做法是在authorized_keys里给公钥加command="/bin/false"前缀,或者用sshd_config的Match User块限制。
第三,云服务器别买内存太小的,如果上面还跑着其他服务,再加我们的转发,1G内存是底线,512M可能会出现OOM把sshd杀掉,通道就断了。
第四,端口号尽量避开常见端口。云服务器经常被扫描,SSH、80、443这些端口是扫描重点,用8080、18080这类高位端口能减少大量扫描噪声。
就分享到这里。我现在的做法是树莓派上跑Nginx挂个人小站,通过autossh维护一条到云服务器的远程转发通道,手机上用浏览器访问时先输账号密码,再看到内网里的页面。整个过程除了云服务器费用外没有额外成本,树莓派功耗低,7x24小时跑完全没压力。如果你正好也想把家里的服务暴露到外网,这套方案值得一试。最后再提醒一句:对外网开放服务一定要设认证,别嫌麻烦,裸奔的站点在公网上活不过一天。