Peer closed connection error with Nginx

Issue Description

Actually, I have my mail server running with Stalwart which is behind an reverse proxy (nginx).

This reverse proxy is working on another server.

So basically the http and https flux are working like this :

Client → NGINX Reverse Proxy (server) → Mail Server

But when I’ve installed Stalwart, the access to the mail dashboard in https worked fine.

And since two day, idk why but I’m getting error 502 : Gateway error from my nginx server.

After looking on the logs, here are the errors :

2026/07/18 00:06:29 [error] 18856#18856: *183933 peer closed connection in SSL handshake (104: Connection reset by peer) while SSL handshaking to upstream, client: <redacted>, server: mail.my-server.fr, request: "GET / HTTP/2.0", upstream: "https://192.168.12.10:443/", host: "mail.my-server.fr"
2026/07/18 00:06:29 [error] 18856#18856: *183933 peer closed connection in SSL handshake (104: Connection reset by peer) while SSL handshaking to upstream, client: <redacted>, server: mail.my-server.fr, request: "GET /favicon.ico HTTP/2.0", upstream: "https://192.168.12.10:443/favicon.ico", host: "mail.my-server.fr", referrer: "https://mail.my-server.fr/"

I’ve tryed a few times to resolve this issue (even with the help of AI) but I can’t find how to resolve it.

Expected Behavior

I need to be able to acces the web dashboard of the mail server running with Stalwart.

Actual Behavior

I’m getting error 502 : Gateway error from my reverse proxy (nginx)

Relevant Log Output

2026/07/18 00:06:29 [error] 18856#18856: *183933 peer closed connection in SSL handshake (104: Connection reset by peer) while SSL handshaking to upstream, client: <redacted>, server: mail.my-server.fr, request: "GET / HTTP/2.0", upstream: "https://192.168.12.10:443/", host: "mail.my-server.fr"
2026/07/18 00:06:29 [error] 18856#18856: *183933 peer closed connection in SSL handshake (104: Connection reset by peer) while SSL handshaking to upstream, client: <redacted>, server: mail.my-server.fr, request: "GET /favicon.ico HTTP/2.0", upstream: "https://192.168.12.10:443/favicon.ico", host: "mail.my-server.fr", referrer: "https://mail.my-server.fr/"

Stalwart Version

v0.16.x

Installation Method

Binary (Linux)

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

Internal

Additional Context

I’m actually running a separate reverse proxy with all the configuration to access all my websites and services.

The Nginx version is : 1.28.3
Both the nginx dedicated server and mail server are running on Ubuntu 26.04 LTS.

I’ve configured the env variable : STALWART_HOSTNAME and STALWART_PUBLIC_NAME to the same domain name.

I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.

on

I have searched this support forum (open and closed topics) and confirm this is not a duplicate.

on

I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.

on

A “peer closed connection during SSL handshake” from nginx means Stalwart is closing the TCP/TLS connection before completing the handshake, which is exactly what happens when the connecting IP (in this case your nginx proxy’s IP) has been auto-banned by Stalwart’s built-in fail2ban-style protection. Since all client traffic reaches Stalwart from the single nginx server IP, that IP can trip the loitering/scan/abuse thresholds and get blocked, after which every connection from it is dropped rather than answered.

Two things to check:

  1. Look in the WebUI under Security › Blocked IPs (or via CLI) for an entry matching your nginx server’s IP (192.168.12.10’s peer, i.e. the address nginx connects from). If it’s there, delete it to restore access immediately.
  2. Add your nginx proxy’s IP as an AllowedIp entry so it bypasses rate limiting and auto-banning going forward, since it represents legitimate proxied traffic rather than a single abusive client:
{
  "ip": "<nginx-server-ip>",
  "reason": "trusted reverse proxy"
}

This is set on the AllowedIp object, found in the WebUI under Settings › Security › Allowed IPs.

Also confirm proxyTrustedNetworks includes your nginx server’s address so Stalwart parses the Proxy Protocol / forwarded client IP correctly rather than banning based on the proxy’s own address for every client’s activity.

Reference: Auto-banning | Stalwart and General | Stalwart (IP address allow list / blocking sections).

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

I unblocked it and put it into the allowIp list. It was also already on the proxyTrustedNetworks. (192.168.12.0/24).

Though, I’m still unable to access the website :slight_smile:

535 peer closed connection in SSL handshake while SSL handshaking to upstream

You have to reload settings for them to take effect.

It could also be that you have the IP in proxyTrustedNetworks without ProxyProtocol enabled. That doesn’t mean “This IP is safe” it means the additional headers are expected. No headers = rejected connection.

You mentionned “ProxyProtocol”. Are we okay that this is related to Nginx ?

If yes, there is the configuration for my mail server on my reverse proxy.

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name mail.redacted.fr autodiscover.redacted.fr autoconfig.redacted.fr;
    ssl_session_timeout 1d;
    ssl_session_tickets off;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
    error_log /var/log/nginx/redacted.log warn;
    access_log /var/log/nginx/redacted.log combined buffer=512k flush=1m;
    add_header Content-Security-Policy "upgrade-insecure-requests" always;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    client_max_body_size 0;
    ssl_certificate redacted.cer;
    ssl_certificate_key redacted.key;
    location /Microsoft-Server-ActiveSync {
        proxy_pass http://<stalwart_server_ip>:8080/Microsoft-Server-ActiveSync;
        proxy_set_header Host $http_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;
        proxy_ssl_server_name on;
        proxy_ssl_name $host;
        proxy_connect_timeout 75;
        proxy_send_timeout 3650;
        proxy_read_timeout 3650;
        proxy_buffers 64 512k;
        client_body_buffer_size 512k;
        client_max_body_size 0;
    }
    location / {
        proxy_pass https://<stalwart_server_ip>;
        proxy_set_header Host $host;
        proxy_http_version 1.1;
        proxy_cache_bypass $http_upgrade;
        proxy_ssl_server_name on;
        proxy_ssl_name mail.redacted.fr;
        # Proxy headers
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header X-Real-IP $http_cf_connecting_ip;
        proxy_set_header Forwarded $proxy_add_forwarded;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port $server_port;
        
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
        
        # Proxy timeouts
        proxy_connect_timeout 300s;
        proxy_send_timeout 300s;
        proxy_read_timeout 300s;
        # Other parameters
        proxy_pass_request_headers on;
        proxy_set_header Authorization $http_authorization;
    }
    location ~ /.well-known/acme-challenge {
        proxy_set_header Host $host;
        proxy_set_header X-Real_IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
        proxy_pass http://127.0.0.1:9180;
    }
}

Note : My reverse proxy is on a different server than where Stalwart is installed since he’s the rev proxy for all my website.

You do not have Proxy Protocol enabled in that configuration.

As before, if you do not have proxy protocol enabled, do not put the IP in Trusted Networks. That will make Stalwart expect Proxy Protocol headers and your connection will be rejected as the headers are missing.

The setting is a bit misleading as it makes you think “These are IPs that should always be trusted to send traffic” but it actually means “These are proxies that are sending Proxy Protocol and can be trusted to send headers indicating what the source IP address is and we can believe them”.

So I need to remove the IP that I have into the “Trusted Networks” ?

Okay, I understand where I went wrong.

Actually, since I have a main server acting as a reverse proxy using NGINX, where I have my websites’ configurations (to redirect them to the correct backend server), I had set everything up that way for Stalwart.

But it turned out I also needed to install NGINX on the server where Stalwart is running, with the Proxy Protocol enabled. Then I added the local IP 127.0.0.1/32 to the “Trusted Networks” setting in Stalwart, and it worked—I no longer get the “Gateway Error”! :sweat_smile:

I should have spotted this error sooner…