Nginx on a Pi: why SSL connections keep timing out after adding SNI for multiple sites

Running a multi‑site reverse proxy on a Raspberry Pi is a classic hobby‑project. The Pi’s low power draw and cheap price make it a go‑to for hosting tiny web services, internal dashboards, or a personal API gateway. Throw TLS in there and you get confidentiality and integrity, but one small mis‑configuration can make every HTTPS request hang until the client gives up.

The usual culprit is the dance between SNI, OpenSSL’s default cipher selection, and the Pi’s limited CPU. Below I’ll walk through why this happens, how to spot it, and the practical tweaks that keep the Pi from blowing up the handshake.


Why the Pi struggles with SNI‑based TLS handshakes

SNI lets a client tell the server which hostname it wants during the TLS handshake. Nginx uses that to pick the right certificate from a list of server blocks. On a desktop or cloud VM the handshake is a handful of microseconds. On a Pi the same handshake can take 200 ms–400 ms when the server must do a full RSA or ECDHE key exchange for each hostname.

The slowdown comes from a few things:

Factor What it does Typical impact on a Pi
RSA key exchange 2048‑bit or 4096‑bit modular exponentiation ~100 ms per handshake
ECDHE with curve P‑256 Elliptic‑curve scalar multiplication ~50 ms per handshake
Cipher suite negotiation OpenSSL may try several suites Adds 10–20 ms per attempt
CPU throttling ARM Cortex‑A72 runs at 1.5 GHz but throttles under load Latency can double

When you add a second site, Nginx must evaluate ssl_certificate and ssl_certificate_key for every server block. If the certificates are large (e.g., 4096‑bit RSA) or the server falls back to a slower cipher, the cumulative cost pushes the handshake past the client’s default timeout (often 30 s). Browsers will show “Connection timed out” or “The connection was reset”.


Detecting the symptom

  1. Client‑side logs
    Chrome’s DevTools → Network → click the failed request → look at the “Status” column. A 408 Request Timeout or ERR_CONNECTION_TIMED_OUT means the TLS layer never finished.

  2. Server‑side timing
    Use openssl s_client to time the handshake:

    time openssl s_client -connect example.com:443 -servername example.com
    

    If the time output shows > 30 s, you’ve hit the timeout.

  3. Nginx access logs
    Add $ssl_handshake_time to your log format:

    log_format ssl '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    '$ssl_handshake_time';
    

    A - means the handshake never finished.


Common mis‑configurations that trigger timeouts

Mis‑config Why it hurts Fix
Single ssl_certificate block for all sites Nginx still performs a full SNI lookup, but the same key is read for every request, causing extra disk I/O Separate ssl_certificate directives per server block
Storing certificates on a slow SD card Each handshake reads the key file from flash, adding ~10 ms per request Move certificates to RAM (/dev/shm) or use a faster SD card (UHS‑I)
Leaving ssl_ciphers at the default OpenSSL may try many ciphers before finding a match Explicitly set a short, secure list: ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384';
Enabling ssl_prefer_server_ciphers on; on a Pi Forces the server to try its cipher list first, which may include slower suites Disable or reorder the list to favor fast ECDHE suites
Using 4096‑bit RSA keys RSA key exchange is CPU‑heavy Switch to 2048‑bit RSA or, better, ECDSA/ECDHE

Practical mitigation steps

1. Switch to ECDHE‑RSA or ECDSA

ECDHE with a 256‑bit curve is roughly 5× faster than RSA key exchange on ARM. Generate a new key pair:

openssl ecparam -name prime256v1 -genkey -noout -out ecdsa.key
openssl req -new -x509 -key ecdsa.key -out ecdsa.crt -days 365

Configure Nginx:

ssl_certificate /etc/nginx/ssl/ecdsa.crt;
ssl_certificate_key /etc/nginx/ssl/ecdsa.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers off;

2. Pin the cipher list to fast suites

A minimal, secure list eliminates the “try‑and‑fail” overhead:

ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';

Add ssl_session_cache shared:SSL:10m; to enable session resumption, which skips the full handshake on subsequent connections.

3. Use ssl_session_tickets wisely

Session tickets are encrypted on the fly. On a Pi, generating the ticket key is cheap, but decrypting it can be expensive if the key is large. Keep the key small (e.g., 256 bits) and rotate it nightly:

openssl rand -hex 32 > /etc/nginx/ssl/ticket.key

Then in Nginx:

ssl_session_tickets on;
ssl_session_ticket_key_file /etc/nginx/ssl/ticket.key;

4. Move certificates to RAM

If your SD card is slow, copy certificates to /dev/shm at boot:

mkdir -p /dev/shm/ssl
cp /etc/nginx/ssl/*.crt /dev/shm/ssl/
cp /etc/nginx/ssl/*.key /dev/shm/ssl/

Update the ssl_certificate paths accordingly.

5. Tune the Pi’s CPU governor

The default ondemand governor throttles the CPU under load. Switch to performance for short bursts:

echo performance | sudo tee /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

Add this to /etc/rc.local to persist across reboots.

6. Verify with openssl s_client

After each change, run:

openssl s_client -connect example.com:443 -servername example.com -tls1_3

Check that the handshake completes within 2 s. Use sshd -T to confirm that Nginx is listening on the correct ports.


Trade‑offs to consider

Change Benefit Risk / Caveat
ECDHE‑RSA over RSA Faster handshakes, forward secrecy Requires clients that support ECDHE (most modern browsers).
Short cipher list Eliminates negotiation overhead May exclude older clients; test with sslscan.
RAM‑based certs Removes disk latency Certificates are lost on reboot unless backed up.
Performance governor Faster CPU Higher power draw; may trigger thermal throttling on older Pi models.

Always balance performance against the attack surface. For example, disabling ssl_prefer_server_ciphers may expose the server to weaker client‑chosen ciphers if the client is compromised. Keep the cipher list tight but inclusive of all clients you expect to serve.


Real‑world example: a Pi‑based API gateway

A security engineer at a small fintech firm runs an Nginx gateway on a Pi Zero W to expose two internal services:

  1. api.internal.company.com – a Node.js microservice
  2. docs.internal.company.com – a static S3‑like bucket

Both services require TLS. After adding SNI, all API calls from the corporate VPN timed out after 30 s. The engineer applied the steps above:

  1. Generated ECDSA certificates for both hosts.
  2. Configured a minimal cipher list and enabled session resumption.
  3. Moved certs to /dev/shm.
  4. Switched the governor to performance.

Post‑fix, the average handshake dropped from 350 ms to 45 ms, and API latency improved from 1.2 s to 0.3 s. No security regressions were observed, and the gateway remained compliant with the firm’s internal policy (see NIST SP 800‑53 Rev.5).


Quick checklist for production

  • Use ECDHE‑RSA or ECDSA certificates (2048‑bit RSA is acceptable if ECDHE is unavailable).
  • Pin the cipher list to fast, secure suites.
  • Enable ssl_session_cache and ssl_session_tickets.
  • Store certificates in RAM or a fast SD card.
  • Set the CPU governor to performance for short bursts.
  • Test with openssl s_client and monitor $ssl_handshake_time in logs.
  • Verify that all clients (browsers, mobile apps, internal tools) support the chosen ciphers.

Final thoughts

SSL/TLS handshakes on a Raspberry Pi are a classic example of how cryptographic choices interact with hardware limits. By understanding the cost of each step in the handshake and applying targeted optimizations, you can keep your multi‑site Nginx deployment responsive without compromising security.

When you add SNI, you’re not just telling Nginx which certificate to use; you’re also triggering a full cryptographic handshake for each hostname. On a low‑power device, that handshake can become the bottleneck. The fixes above are proven in the field and align with best practices from NIST and OWASP.


See also