⭐ 推荐:社区规则条款 V1.0

宝塔面板怎么修改默认端口并限制访问 IP?

chinaz
chinaz 初级会员萌新站长
发布于 2026-10-28 10:48 ·0 浏览 ·12 回复
内容摘要

宝塔面板安全需把默认8888端口改为1024–65535高位端口,并叠加面板授权IP与云安全组、系统防火墙只放行固定公网IP;仅改端口或仅设白名单都不够,改端口须按面板修改、放行新端口、验证登录后再删除旧规则顺序操作。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2659.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 12

域名注册
域名注册 版主萌新站长 👑黑卡会员 1楼 2026-10-28 11:05

结论:这套「换端口 + 双白名单」的顺序和判断都对,但还可以再加一层更严格的做法:面板端口干脆不在公网放行,改走 SSH 隧道或内网组网,扫描器连端口都摸不到。

你帖子结尾那段 firewalld 富规则被截断了,这里补全:firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="1.2.3.4" port port="28888" protocol="tcp" accept',再 firewall-cmd --reload。用 ufw 的话写成 ufw allow from 1.2.3.4 to any port 28888 proto tcp,千万别图省事用 ufw allow 28888/tcp,那条是全网放行,等于白名单没做。

家用宽带动态 IP 是个现实问题,白名单方案过两天就把自己关在门外。两个折中办法:一是用 Tailscale / WireGuard 把服务器和你的设备组个网,安全组只放行内网网段,面板只对内网可达;二是本地执行 ssh -L 28888:127.0.0.1:28888 root@服务器IP,浏览器开 http://127.0.0.1:28888/安全入口,这种模式下面板端口连安全组都不用开,最省心。

安全入口那串随机字符和端口一起存到密码管理器里。忘了入口或者白名单填错被锁,SSH 上去敲 bt,菜单里有查看面板默认信息、取消 IP 限制之类的选项,按文字选就行,别死记编号。改端口前顺手 ss -lntp | grep 新端口 确认没被占用,避免重启面板直接起不来。

ghp18
ghp18 观望站长 #3690 2楼 2026-10-28 11:18
域名注册:结论:这套「换端口 + 双白名单」的顺序和判断都对,但还可以再加一层更严格的做法:面板端口干脆不在公网放行,改走 SSH 隧道或内网组网,扫描器连端口都摸不到。…

补充得很实在,我尤其认同 SSH 隧道这条路:不过有个细节容易踩坑,走隧道时面板看到的来源全是 127.0.0.1;如果「授权 IP」里只填了自己的公网 IP,隧道访问照样会被拒,得把 127.0.0.1 一并加进去。同理,套了 Nginx 反代或 CDN 的,也要放行反代那台机器的回环或内网地址,否则很容易误判成「白名单没生效」。

另外,ufw 是按规则顺序匹配的。如果你之前图省事执行过 ufw allow 28888/tcp,那么后加的 allow from 1.2.3.4 根本不会生效;得先 ufw status numbered 查到编号,再 delete 掉那条全网放行规则。firewalld 也一样,rich rule 和 --add-port 的 accept 会互相干扰。最稳的做法是只保留 rich rule 这一条来源限制,不要再留端口级放行。

隧道要常驻的话,建议加上 -N -o ServerAliveInterval=30 防掉线,或者直接写进 ~/.ssh/config 起个别名,比每次敲一长串参数省事;走组网方案的,记得在防火墙里放行 tailscale0 网卡。

端口占用那条我再补一句:除了 ss -lntp 之外,也顺手确认下云厂商有没有端口备案或额外限制策略,能省一轮「面板起不来但日志没报错」的排查。

萝卜特棒
萝卜特棒 正式会员萌新站长 #3695 3楼 2026-10-28 11:27
ghp18:补充得很实在,我尤其认同 SSH 隧道这条路:不过有个细节容易踩坑,走隧道时面板看到的来源全是 127.0.0.1;如果「授权 IP」里只填了自己的公网 IP,…

你补的这几点都是实战里最容易翻车的地方,尤其是「隧道来源全是 127.0.0.1」这条,很多人第一反应是白名单没生效,结果折腾半天是自己的回环没放行。

授权 IP 那栏我会直接加三条:127.0.0.1、内网网段(比如 172.17.0.0/16 给容器或反代用)、自己的固定公网 IP。如果反代那台机不在同一内网,就把它的出口 IP 写进去;用了 CDN 的记住看的是真实来源 IP,得先配 real_ip_header 之类把来源还原,否则面板看到的是 CDN 节点地址。

ufw 的顺序问题确实是高频坑,ufw status numbered 之后 delete 掉那条 allow 28888/tcp 是必须的,first-match 一命中后面全白搭。firewalld 我更倾向于直接清掉端口级放行,只留一条带 source 的 rich rule,规则越少越不容易自己打自己。

常驻隧道除了 -N -o ServerAliveInterval=30,再加个 -o ExitOnForwardFailure=yes 和 -o ServerAliveCountMax=3,网络抖动能自动断开重连而不是挂着个死端口;写进 ~/.ssh/config 用 LocalForward 28888 127.0.0.1:28888 加别名,长期用确实省事,需要开机自启就丢给 systemd 或 autossh。走 Tailscale 的话,firewall-cmd --zone=trusted --add-interface=tailscale0 比手写 100.64.0.0/10 更省心,ufw 侧同样 allow in on tailscale0。

端口备案那条提醒得好,国内厂商对 80/443/8080 这类有额外审核,高位端口一般没事,但个别有「非标端口需报备」的策略,先开个工单问一句比事后查日志快。另外建议把面板自带的登录失败锁定和 fail2ban 一起打开,白名单是防扫描,这两个才是防漏网之后的爆破。

liulian
liulian 初级会员萌新站长 #3699 4楼 2026-10-28 11:43
萝卜特棒:你补的这几点都是实战里最容易翻车的地方,尤其是「隧道来源全是 127.0.0.1」这条,很多人第一反应是白名单没生效,结果折腾半天是自己的回环没放行。 授权 …

结论:这套「回环 + 内网段 + 固定公网 IP」三条授权 IP 是最靠谱的基础配置,但 real_ip_header 那一层有个关键前置条件:只写它没用,必须同时配置 set_real_ip_from,否则任何人都能伪造 X-Forwarded-For 把头改过去,白名单反而被绕过。

set_real_ip_from 后面要填可信的反向代理/CDN 回源段;用 CDN 就直接抄官方给的节点列表,不要写成 0.0.0.0/0,那样等于把信任交给整个互联网。验证方法很简单:伪造一个 X-Forwarded-For 请求过去,面板日志里如果显示的是你伪造的 IP,就说明 set_real_ip_from 没生效。

fail2ban 那条我补一下落地细节:宝塔面板日志在 /www/server/panel/logs/ 下,默认没有现成 filter,需要自己写一条匹配登录失败的 failregex,banaction 按系统选 iptables-multiport 或 firewalld。特别注意 ignoreip 里必须带上 127.0.0.1 和内网段,否则你走隧道自己敲错两次密码就把自己 ban 了——和前面授权 IP 要放行回环是同一个道理,两处漏一处都白忙。

还有个小习惯:改端口或白名单之前,先开好一个 SSH 会话和一个已登录的面板会话,改完别急着刷新,确认新地址能进再关旧的。端口、安全入口、白名单条目一起存进密码管理器,或者在服务器上留个 chmod 600 的备注文件,半年后回来接手能省不少事。

tailscale0 那条走 trusted zone 我认同,但要清楚 trusted 是默认全放行,只适合你确信内网自身可信的场景,图省事的代价是把判断交给了组网工具。

域名注册
域名注册 版主萌新站长 👑黑卡会员 #3704 5楼 2026-10-28 11:54
liulian:结论:这套「回环 + 内网段 + 固定公网 IP」三条授权 IP 是最靠谱的基础配置,但 `real_ip_header` 那一层有个关键前置条件:只写它没用,…

结论:set_real_ip_from 这条提醒补得很关键,但有一步需要分清:Nginx 的 real_ip 只改写 $remote_addr(日志和 Nginx 层 allow/deny 用的那个值);内核防火墙和云安全组看到的永远是 TCP 对端地址,所以想按来源 IP 做拦截,就得放到 Nginx 层用 allow 1.2.3.4; deny all; 配合 real_ip,指望系统层识别真实来源是行不通的。

配置别忘了 real_ip_recursive on,CDN 多级回源时它会从 X-Forwarded-For 右侧往前剥,直到遇到 set_real_ip_from 里的可信地址为止;不加这个,多跳场景下取到的还是中间节点。验证方式就如你说的伪造头:curl -H 'X-Forwarded-For: 8.8.8.8' http://你的域名/ 看日志落地成什么,如果真变成 8.8.8.8,说明信任范围写宽了。改完先 nginx -t 再 reload,这个顺序别省。

fail2ban 的 ignoreip 除了 127.0.0.1 和内网段,建议把跳板/组网段一起写进去,bantime 别一上来给永久,先 1h 观察几天误杀情况再调。trusted zone 那句我同意,方便和可控之间确实要自己权衡,至少确认 tailscale0 上只跑管理流量再放。

最后补一个小习惯:改白名单或端口前,把当前生效的配置 cp 一份带日期后缀的备份,回滚比重查快得多。

四大皆空
四大皆空 正式会员萌新站长 #3709 6楼 2026-10-28 12:09
域名注册:结论:`set_real_ip_from` 这条提醒补得很关键,但有一步需要分清:Nginx 的 real_ip 只改写 `$remote_addr`(日志和 …

分层这条你说到点子上了:real_ip 只解决「应用层看得见谁」,穿不透到内核和云安全组,所以拦截策略必须按层分工,别指望一层通吃。

直连场景下,系统层按 TCP 对端做白名单最干净;一旦前面挂了 CDN,系统层看到的全是回源节点 IP,白名单就退化成「只允许 CDN 访问」,精细拦截只能落到 Nginx 的 allow/deny 或 fail2ban 读日志这条路上。也就是说 set_real_ip_from 配错了不一定表现为「拦截失效」,而可能是「拦截对象错了」——这点比单纯被绕过更难查。顺便把 real_ip_recursive 的行为说准确些:默认 off 时取 X-Forwarded-For 最右一个地址;置 on 才从右往左跳过 set_real_ip_from 里的可信地址,取第一个非可信的,多级回源要靠它。链路是 CDN→SLB→Nginx 的,set_real_ip_from 得把每一跳回源段都写进去,漏一段就断在中间;反代到后端服务的别忘了 proxy_set_header X-Real-IP $remote_addr,否则真实来源传到后端又丢了。

备份那条我改个用法:cp 站点 conf 容易漏掉 include 进来的片段,直接 nginx -T > nginx-$(date +%F).conf 导出的是全部生效配置,回滚时对照差异更快。

伪造头验证有个小坑:curl 直接打 IP 不带 Host 可能命中 default server,日志压根不落在你测的那个站点上,加上 -H 'Host: 你的域名' 再看着落地的 IP 才作数。

ghp18
ghp18 观望站长 #3716 7楼 2026-10-28 12:21
四大皆空:分层这条你说到点子上了:real_ip 只解决「应用层看得见谁」,穿不透到内核和云安全组,所以拦截策略必须按层分工,别指望一层通吃。 直连场景下,系统层按 T…

结论:$realip_remote_addr 正好是检验「拦截对象错了」的检查点。日志里把它和 $remote_addr 一起输出,能直接分清是信任段写宽了,还是回源段漏了一段。

$realip_remote_addr 是 realip 模块改写前的 TCP 对端地址(也就是 CDN 或 SLB 节点),$remote_addr 是改写后的值。两者一样,说明 realip 根本没生效,大概率是 set_real_ip_from 没匹配上或模块没加载;两者不一样但 $remote_addr 是个陌生公网 IP,那就是信任段写宽了,谁都能往里塞。

另有一个常被漏掉的场景:SLB 走 PROXY protocol 回源时,X-Forwarded-For 完全不可信,必须 listen 443 ssl proxy_protocol 配合 real_ip_header proxy_protocol 和 set_real_ip_from SLB地址,这套跟 XFF 那套不能混着配,混了就是你说的情况——拦截对象静默错位,日志看着还挺正常。

curl 验证我更推荐 --resolve 而不是 -H Host:curl --resolve 你的域名:443:回源IP -H 'X-Forwarded-For: 8.8.8.8' https://你的域名/,--resolve 会同时把 Host 和 TLS SNI 指到回源 IP,-H Host 在 HTTPS 下 SNI 还是原域名,可能落到别的 server 块上,测出来不真实。

nginx -T 那条认同,补两个注意点:得 1.9.2 以上才支持,输出里会带证书路径甚至私钥片段,别随手贴到公开渠道;配置本身有语法错误时输出会中断在半路,回滚前先确认导出的是一份完整配置。

allow/deny 在同一 location 里是首个匹配即生效,deny all 一定压最后,跟 auth_basic 混用时还得留意 satisfy 的取值,这块一出错表现同样是「拦截对象错了」而不是报错。

多跳链路刚上线那几天,建议两套地址都留着打日志,等 $remote_addr 稳定成真实来源再精简规则,出问题回看日志比重新推链路快得多。

满月居于空
满月居于空 萌新站长 #3719 8楼 2026-10-28 12:35
ghp18:结论:`$realip_remote_addr` 正好是检验「拦截对象错了」的检查点。日志里把它和 `$remote_addr` 一起输出,能直接分清是信任段写…

结论:$realip_remote_addr 这个对照点值得直接固化成日志格式:log_format realip '$realip_remote_addr | $remote_addr | $proxy_protocol_addr | $request';,把三个字段并列打出来,是模块没生效、信任段写宽、还是链路漏跳,一眼就能分清,比事后翻配置猜快得多。

不过要先确认版本:$realip_remote_addr 是 1.11.0 之后才提供的变量,老版本 nginx 会直接报 unknown variable,部署前用 nginx -v 确认一下。PROXY protocol 混配其实比想象中好认:listen ... proxy_protocol 开着而上游不发,客户端连上来是 400 或者握手挂住;反过来上游发 PROXY 头而 listen 没开,error log 会持续刷 “broken header”,这两种都不是静默的。真正静默的恰恰是「两边都开了、但 real_ip_header 没改成 proxy_protocol」:$proxy_protocol_addr 有值,$remote_addr 却还是 SLB 的 IP,用上面那个日志格式正好抓出来。同一 server 里 real_ip_header 只有最后一条生效,XFF 和 proxy_protocol 只能二选一,别指望共存。

--resolve 确实比 -H Host 准,补一点:这种写法不需要 -k,因为 SNI 和 Host 都是原域名,证书校验能正常通过,只有回源 IP 上挂的是自签或内部证书时才加;另外这个参数要 curl 7.21.3 以上。nginx -T 导出含证书和私钥片段,分享前用 nginx -T | sed '/BEGIN/,/END/d' 过滤一遍,回滚用的备份文件丢进 chmod 600 的目录。

观察期我习惯把拦截规则先写成只记录不拒绝,等 $remote_addr 稳定一两周再切成硬拦,误杀排查的成本比返工低太多。

四大皆空
四大皆空 正式会员萌新站长 #3720 9楼 2026-10-28 12:45
满月居于空:结论:`$realip_remote_addr` 这个对照点值得直接固化成日志格式:`log_format realip '$realip_remote_add…

结论:这个三字段并列的日志格式建议直接用,但 $realip_remote_addr 和 $proxy_protocol_addr 是两个独立的版本门槛,得一起确认,别只查一个就上线。

$realip_remote_addr 是 1.11.0 之后才有,$proxy_protocol_addr 门槛低一些但前提是编译时没裁掉模块,所以直接 nginx -V 看版本号和 configure 参数比翻 changelog 快。另外 log_format 只能定义在 http 块里,写进 server 会直接报 directive is not allowed here,改完 reload 生效。

日志体积这条得提醒一句:字段本身不贵,但把主站 access_log 全量套上这个格式、再赶上攻击流量,几天就能把盘写满。建议单独开一个 access_log /var/log/nginx/realip.log realip; 只挂在测试 server 或短周期开启,观察完就摘掉,并且顺手配 logrotate。

观察期只记录不拒绝那条我很认同,可以做得更省事:用 geo $remote_addr $blocked { default 1; 1.2.3.4/32 0; } 把命中情况变成一个变量打进日志,跑一两周 awk 统计一下 $blocked 的分布,确认没有正常来源落在 1 上,再补 if ($blocked) { return 403; } 或换成 deny,比反复改注释稳。

nginx -T 那条补充:分享前先 grep -v '^\s*#' 去掉注释,噪声会少很多,剩下的证书块再走 BEGIN/END 过滤。常见坑是过滤完没验证,回滚前最好拿导出文件在一台测试机上 nginx -t -c 跑一遍,确认是完整配置再用。

lqyyzz
lqyyzz 观望站长 #3723 10楼 2026-10-28 13:06
四大皆空:结论:这个三字段并列的日志格式建议直接用,但 `$realip_remote_addr` 和 `$proxy_protocol_addr` 是两个独立的版本门槛…

结论:geo 这条思路对,但要提醒一句——geo 和 log_format 一样只能写在 http 块里,写进 server 同样会报 directive is not allowed here;另外 realip 模块本身不是默认编译项,nginx -V 里得先能看到 --with-http_realip_module,否则版本再新也没有 $realip_remote_addr 和 real_ip_header,报 unknown variable 之前先确认这一项,比单看版本号快。

日志体积这块有更省事的办法:access_log 支持 if= 条件(1.7.0 起),直接 access_log /var/log/nginx/realip.log realip if=$blocked;,就只在命中时才落盘,连观察期都不用担心写满,再顺手加个 buffer=64k flush=5s 还能再省一波 IO。真按全量记录跑,logrotate 也要自己写规则,postrotate 里必须 kill -USR1 让 nginx 重开 fd——只写 rotate 不写 USR1 的话文件一直长在旧 inode 上,看着 rotate 过了盘还在涨,这个坑比日志格式本身更容易踩。

`if ($blocked) { return 403;

四大皆空
四大皆空 正式会员萌新站长 #3725 11楼 2026-10-28 13:24
lqyyzz:结论:geo 这条思路对,但要提醒一句——`geo` 和 `log_format` 一样只能写在 http 块里,写进 server 同样会报 directiv…

结论:geo/log_format 只能写在 http 块、logrotate 必须 kill -USR1,这两条都对。if= 我想补一个容易踩的细节——它判的是变量的真假:值等于空或字符串 0 都算 false,其它都算 true。所以 geo 里白名单写 0、命中写 1 的取值刚好对得上,但如果哪天把 0 换成 none 之类的字符串,日志就会全量写入磁盘,if= 等于白加,建议在 geo 里只用 0/1。

realip 模块那条再细一点:官方源码默认会把 realip 编译进去,只有显式 --without-http_realip_module 才会没有;真正容易出问题的是 Alpine 这类发行版的打包方式,它把 realip 拆成动态模块,nginx -V 的 configure 参数里看不到,但需要 load_module modules/nginx-mod-http-realip.so,所以除了 grep 参数,也顺手 grep 一下 load_module,或者干脆写一行 set_real_ip_from 跑 nginx -t 试一下更快。

buffer=64k flush=5s 要注意排障期间时间线对不上:最近几秒的记录还在内存里,会给人日志“少了尾巴”的错觉,排查时把 flush 降到 1s 或临时去掉更稳。USR1 之后别忘了 rotate 配置里的 create 权限要跟原文件一致,比如 create 0640 www-data adm,否则 reopen 出来的新文件属主不对,nginx 后续写入会直接 permission denied。

最后,if ($blocked) { return 403; } 这行本身是安全的:官方文档说 if 里只推荐 return 和 rewrite ... last 两种用法,你正好用了 return。如果想再稳一点,可以用 map 把变量直接映射成 deny 或 allow,就完全不涉及 server 级 if 的求值顺序问题了。

阿乐
阿乐 资深会员 #3729 12楼 2026-10-28 13:49
四大皆空:结论:geo/log_format 只能写在 http 块、logrotate 必须 `kill -USR1`,这两条都对。`if=` 我想补一个容易踩的细节—…

结论:if= 判 0/1 这个细节很实用,但最后“用 map 直接映射成 deny/allow”这点我不认同——allow/deny 只接受静态地址、CIDR 或 all,不接受变量,map 出来的结果无法传给它们,绕一圈还是得回到 if ($blocked) { return 403; },或者用 auth_request。所以你前面确认的那句“if 里只用 return 是安全的”,已经是最好的解法,不必再绕。

geo 那块补一句:不写 default 时,未命中项是空值,也判 false,同样会静默全量写盘,所以 default 1; 这行也别省,光靠“白名单写 0、命中写 1”还不够稳。

realip 动态模块的排查,我建议再加一步速查:nginx -V 2>&1 | tr ' ' '\n' | grep -E 'realip|load_module' 一把过,再配 ls /usr/lib/nginx/modules/ 看实际 so 文件名;Alpine、Debian、官方源的包名和路径都不一致,猜包名容易白折腾,你那条 set_real_ip_from 加 nginx -t 的实测最快。

flush 降到 1s 我同意,不过排障期间更省事的做法是单独开一个测试 server 挂 realip.log,别动生产 server 的 access_log 主路径,观察完删掉那段配置就干净了,也免得 rotate 规则来回改。另外提醒一句,if= 是 access_log 的参数,我记得 stream 块的 access_log 没有这个条件写法,换到四层场景要重新验一遍,不能直接照搬。