1Panel 部署 Stalwart + Roundcube 遇到的两个网络问题总结
一、环境
服务器系统:
Debian 13
Kernel: 6.12.x
Docker: 29.8.1
Firewall Backend: iptables
通过 1Panel 应用商店部署:
Stalwart Mail Server
image: stalwartlabs/stalwart:v0.16.22-alpine
Roundcube
image: roundcube/roundcubemail:1.7.4-apache
PostgreSQL
OpenResty
Docker 网络:
1panel-network
Driver: bridge
Subnet: 172.18.0.0/16
Gateway: 172.18.0.1
当时容器 IP:
Stalwart:
172.18.0.2
PostgreSQL:
172.18.0.3
Roundcube:
172.18.0.4
OpenResty 使用:
NetworkMode=host
邮件域名:
mail.xfirco.com
Webmail:
webmail.xfirco.com
二、问题 1:Roundcube 无法通过公网邮件域名连接 Stalwart IMAP
Roundcube 在 1Panel 中配置:
IMAP:
ssl://mail.xfirco.com
993
SMTP:
tls://mail.xfirco.com
587
Roundcube 登录时出现:
连接至 IMAP 服务器失败
但从宿主机测试:
openssl s_client \
-connect mail.xfirco.com:993 \
-servername mail.xfirco.com
完全正常:
Verification: OK
* OK [CAPABILITY ...] Stalwart IMAP4rev2 at your service.
说明:
DNS 正常
993 正常
TLS 正常
证书正常
Stalwart IMAP 正常
但在 Roundcube 容器内:
openssl s_client \
-connect mail.xfirco.com:993 \
-servername mail.xfirco.com
返回:
SSL routines::unexpected eof while reading
PHP:
stream_socket_client("ssl://mail.xfirco.com:993")
也返回:
Failed to enable crypto
Unable to connect
原因定位
Roundcube:
172.18.0.4
Stalwart:
172.18.0.2
都处于:
br-8b523a9d6c9c
Docker 自动生成的 993 DNAT 规则:
-A DOCKER
! -i br-8b523a9d6c9c
-p tcp
--dport 993
-j DNAT
--to-destination 172.18.0.2:993
关键是:
! -i br-8b523a9d6c9c
也就是说:
从同一个 1panel-network bridge 进入的数据包,不使用宿主机 published port 的这条 DNAT 规则。
因此:
Roundcube
→ mail.xfirco.com
→ 公网 IP
→ 宿主机 993
→ Stalwart
这条路径不正常。
但直接走 Docker 内网:
openssl s_client \
-connect stalwart-mail:993 \
-servername mail.xfirco.com
完全正常。
当前解决方案
Roundcube 不再通过公网 IP 回环访问 Stalwart,而是:
Roundcube
→ Docker DNS
→ stalwart-mail
→ Stalwart
同时为了保持 TLS 证书验证正常:
实际 TCP 主机:
stalwart-mail
TLS SNI / peer_name:
mail.xfirco.com
Roundcube 自定义配置:
$config['imap_host'] = 'ssl://stalwart-mail:993';
$config['imap_conn_options'] = [
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'peer_name' => 'mail.xfirco.com',
'SNI_enabled' => true,
],
];
$config['smtp_host'] = 'tls://stalwart-mail:587';
$config['smtp_user'] = '%u';
$config['smtp_pass'] = '%p';
$config['smtp_conn_options'] = [
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'peer_name' => 'mail.xfirco.com',
'SNI_enabled' => true,
],
];
实测:
Roundcube → Stalwart IMAP
正常
Roundcube → Stalwart SMTP
正常
三、问题 2:Stalwart Web 管理页面突然出现 502
访问:
https://mail.xfirco.com
出现:
502 Bad Gateway
openresty
1Panel 反向代理:
https://mail.xfirco.com
→ OpenResty
→ http://127.0.0.1:18080
→ Stalwart 8080
Stalwart Docker 映射:
127.0.0.1:18080 → 8080
现象
执行:
curl -v http://127.0.0.1:18080/
TCP 能连接:
Connected to 127.0.0.1 port 18080
但随后:
Empty reply from server
OpenResty 日志:
upstream prematurely closed connection
while reading response header from upstream
不是:
connection refused
所以不是 OpenResty 找不到端口,而是 Stalwart 主动关闭连接。
四、发现 Stalwart 把 Docker 网关封禁了
Stalwart 日志:
Blocked IP address
listenerId = "http"
localPort = 8080
remoteIp = 172.18.0.1
查询 BlockedIp:
IP Address:
172.18.0.1
Reason:
Excessive port scanning attempts
Expiration:
<none>
也就是:
172.18.0.1
被永久封禁。
而这个地址正好是:
1panel-network Gateway
172.18.0.1
为什么发生
公网有大量扫描:
/.env
/wp-config.php
/phpinfo.php
/.git/config
/database.sql
/server.key
...
OpenResty 将请求反代给 Stalwart。
Stalwart 当时看到所有 HTTP 请求来源基本都是:
172.18.0.1
于是扫描器触发安全规则后,Stalwart 把:
172.18.0.1
当成攻击者封掉。
结果:
所有 OpenResty → Stalwart Web 请求
全部被关闭
→ 502
五、解除封禁
使用 Stalwart CLI Docker:
docker run --rm \
--network container:1Panel-stalwart-mail-HzwT \
-e STALWART_URL=http://127.0.0.1:8080 \
-e STALWART_USER=管理员 \
-e STALWART_PASSWORD \
ghcr.io/stalwartlabs/cli:latest \
query BlockedIp \
--where address=172.18.0.1
得到:
jfbzyasgabae
172.18.0.1
Excessive port scanning attempts
删除:
delete BlockedIp --ids jfbzyasgabae
然后加入:
AllowedIp:
172.18.0.1
避免 OpenResty / Docker 网关再次被整体封禁。
六、1Panel 实际已经正确传递真实 IP Header
检查 1Panel 自动生成的:
/www/sites/mail.xfirco.com/proxy/root.conf
内容:
location ^~ / {
proxy_pass http://127.0.0.1:18080;
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 REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_http_version 1.1;
add_header X-Cache $upstream_cache_status;
proxy_ssl_server_name off;
proxy_ssl_name $proxy_host;
}
因此 1Panel/OpenResty 本身已经发送:
X-Real-IP
X-Forwarded-For
X-Forwarded-Proto
七、Stalwart 开启 X-Forwarded-For
配置:
Http.useXForwarded = true
CLI:
update Http --field useXForwarded=true
确认:
Proxy
Obtain remote IP from Forwarded header: Yes
因此最终采用:
OpenResty
→ X-Forwarded-For
→ Stalwart
作为真实 IP 传递方式。
八、中途踩到的另一个坑:proxyTrustedNetworks
曾尝试设置:
proxyTrustedNetworks:
172.18.0.1
设置后:
curl http://127.0.0.1:18080/
再次出现:
Empty reply from server
OpenResty:
upstream prematurely closed connection
清空:
proxyTrustedNetworks = <none>
重启 Stalwart 后:
curl -v http://127.0.0.1:18080/
立即恢复:
HTTP/1.1 302 Found
location: /account
所以当前架构下:
useXForwarded = true
适用于 HTTP X-Forwarded-For。
而:
proxyTrustedNetworks
会影响 PROXY Protocol 行为,不能和当前普通 HTTP 反代方式混用。
九、最终稳定配置
现在使用:
1Panel / OpenResty
↓
HTTP Reverse Proxy
↓
127.0.0.1:18080
↓
Stalwart :8080
OpenResty:
X-Real-IP
X-Forwarded-For
X-Forwarded-Proto
Stalwart:
Http.useXForwarded = true
proxyTrustedNetworks = <none>
AllowedIp:
172.18.0.1
目前:
Stalwart Web 正常
Roundcube 正常
IMAP 正常
SMTP 465 / 587 正常
十、希望 1Panel 官方考虑改进的地方
1. Stalwart 应用模板和反向代理联动
目前用户通过 1Panel 应用商店安装 Stalwart 后,很容易采用:
127.0.0.1:18080 → Stalwart 8080
再通过 1Panel OpenResty 反代。
建议在 Stalwart 应用说明中明确提示:
如果通过 1Panel OpenResty 反向代理 Stalwart:
应开启 Stalwart Http.useXForwarded
并确认 X-Forwarded-For 正确传递。
否则存在:
公网扫描器
→ OpenResty
→ Stalwart
→ Docker Gateway 被误判攻击源
→ 172.18.0.1 被封
→ 整个 WebUI 502
的问题。
2. 建议防止代理网关被永久 Auto-Ban
如果 1Panel 能识别应用是反代服务,可以考虑:
OpenResty / Docker Gateway
加入可信/允许地址,或者至少在应用安装说明中明确提醒。
尤其当前 Stalwart 对扫描封禁:
Expiration: <none>
意味着一旦误封代理地址,WebUI 会永久不可访问,必须 SSH 解封。
3. Roundcube + Mail Server 同网络场景
Roundcube 与 Stalwart 都挂在:
1panel-network
时,如果 Roundcube 配置:
ssl://mail.example.com:993
而这个域名又解析到宿主机公网 IP,可能遇到 Docker bridge → published port → 同 bridge 容器的 NAT 回环问题。
建议 1Panel 的邮件套件文档中明确推荐:
Roundcube
→ Docker service name
→ Stalwart
而不是:
Roundcube
→ 公网域名
→ 公网IP
→ 宿主机 published port
→ Stalwart
同时考虑 TLS SNI/peer_name 的处理。
十一、最小复现
Roundcube 问题
同一 Docker bridge:
Stalwart:
172.18.0.2
Roundcube:
172.18.0.4
Roundcube:
mail.example.com → 宿主机公网IP
执行:
docker exec roundcube \
openssl s_client \
-connect mail.example.com:993 \
-servername mail.example.com
失败。
执行:
docker exec roundcube \
openssl s_client \
-connect stalwart-mail:993 \
-servername mail.example.com
成功。
Stalwart 502 问题
OpenResty:
host network
Stalwart:
127.0.0.1:18080 → container:8080
Stalwart 将:
172.18.0.1
加入 BlockedIp 后:
curl http://127.0.0.1:18080/
表现为:
TCP connected
Empty reply from server
OpenResty:
upstream prematurely closed connection
最终:
502 Bad Gateway
删除 BlockedIp 后恢复。