ARTICLE DETAIL

资讯详情

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

树莓派Nginx+SSH远程转发:无公网IP也能远程访问内网Web

树莓派Nginx+SSH远程转发:无公网IP也能远程访问内网Web 最近给树莓派折腾了一个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.htmlsudo mkdir -p /var/www/myweb echo htmlheadtitlePi Site/title/headbodyh1Hello from Raspberry Pi/h1/body/html | sudo tee /var/www/myweb/index.html然后新建站点配置文件/etc/nginx/sites-available/mywebserver { 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。然后在服务器上创建一个专门用来转发的用户比如tunnelsudo 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保持会话并自动重连。在树莓派安装autosshsudo apt install autossh -y然后创建一个systemd服务让它开机自启sudo nano /etc/systemd/system/web-tunnel.service内容如下[Unit] DescriptionSSH Remote Forward Tunnel Afternetwork-online.target Wantsnetwork-online.target [Service] Userpi ExecStart/usr/bin/autossh -M 0 -N -R 8080:localhost:80 tunnel云服务器IP ExecStop/usr/bin/kill $MAINPID Restartalways RestartSec5 EnvironmentAUTOSSH_GATETIME0 [Install] WantedBymulti-user.target参数解析-M 0表示关闭autossh自带的监测端口和systemd配合时不需要它Restartalways让服务挂掉后自动拉起AUTOSSH_GATETIME0让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; }重新加载Nginxsudo nginx -t sudo systemctl reload nginx再次访问时浏览器会弹出一个登录框输入webuser和密码才能看到内容。认证是在Nginx这一层完成的即使有人扫到云服务器的8080端口也会被登录框挡住不会直接看到网站。4.2 如何引入HTTPS加密HTTP是明文传输在公共WiFi下别人可以抓包看到页面内容。理想情况是启用HTTPS有两个方向第一种如果你有自己的域名把域名解析到云服务器IP然后在Nginx里申请Lets 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 /CNmyweb.local然后修改Nginx配置监听443并加载证书。我这里目前用的是HTTPBasic 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_logaccess_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页面404root路径配置错误检查站点根目录是否真实存在文件是否在这个目录下外网能访问但网页资源加载慢未开启gzip、图片体积大、SSH链路质量差开gzip用压缩图片观察链路延迟通道反复断开SSH连接被路由器切断、云服务器断开空闲连接用autossh自动重连设置Restartalways5.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小时跑完全没压力。如果你正好也想把家里的服务暴露到外网这套方案值得一试。最后再提醒一句对外网开放服务一定要设认证别嫌麻烦裸奔的站点在公网上活不过一天。
返回列表