The symptom looks like a capacity problem: Apache processes pinned near 100% CPU, request latency climbing, and no obvious cause in the access log. Traffic is up, but not absurdly. The workers are not stuck on a backend. The scoreboard is busy but not exhausted. And yet the box is melting.
On HTTPS-heavy servers, the usual suspect is the TLS handshake. A full TLS handshake with an RSA-2048 certificate costs on the order of 15 ms of CPU time per connection. Session resumption cuts that cost by roughly 10x. When resumption works, repeat clients skip the expensive public-key operation entirely. When resumption is broken, every connection pays the full price, and at a few hundred new connections per second the math stops working.
That is the HTTPS CPU wall: the point where the cost of full handshakes per second exceeds the CPU you have, and no amount of worker tuning gets it back. This article covers how to confirm that broken session resumption is the cause, why resumption breaks in Apache, and how to fix it.
What this means
TLS offers two ways to resume a session: session IDs (server-side state) and session tickets (RFC 5077, client-held encrypted state). Apache 2.4’s defaults quietly undermine the first mechanism:
SSLSessionCachedefaults tonone. The inter-process session cache is disabled entirely out of the box.- Without a shared cache, a session ID is only valid in the child process that created it. With many children and a load-balanced client reconnecting to a random child, cache hits are rare.
- Session tickets are on by default (
SSLSessionTickets on) and work across children, but they have their own traps: Apache generates one random ticket key at startup and never rotates it, and with TLS 1.3 a graceful restart can silently break ticket-based resumption.
The result is a server where most reconnecting clients do a full handshake anyway. Because a full handshake is the single most CPU-intensive thing Apache does, the failure shows up as a CPU problem, not as a TLS error. Nothing is logged. A silently full or absent session cache means old sessions evicted, resumption rate drops, CPU increases, and no error logged anywhere.
flowchart TD
A[New connection to :443] --> B{Session resumed?}
B -- yes --> C[Abbreviated handshake
low CPU]
B -- no --> D[Full handshake
~15 ms CPU with RSA-2048]
E[Resumption broken:
SSLSessionCache none,
shmcb missing or too small,
TLS 1.3 + graceful restart] --> B
D --> F[CPU saturated at
moderate connection rate]
F --> G[Latency climbs,
handshake queue grows]Common causes
| Cause | What it looks like | First thing to check |
|---|---|---|
SSLSessionCache none (the default) | Low resumption rate since forever; CPU tracks new-connection rate linearly | apachectl -t -D DUMP_RUN_CFG or grep the config for SSLSessionCache |
shmcb cache too small | Resumption worked at low traffic, degraded as traffic grew; no error logged | Cache size vs concurrent session count; test resumption under load |
mod_socache_shmcb not loaded (2.2 to 2.4 upgrade) | Config references shmcb but startup fails or cache is silently unsupported | Error log for SSLSessionCache: 'shmcb' session cache not supported |
| TLS 1.3 resumption broken after graceful restart | Resumption works after a full restart, stops after apachectl graceful | Test openssl s_client -reconnect before and after a graceful restart |
| Session tickets disabled, cache not shared | Full handshakes for every client that lands on a different child | Check SSLSessionTickets and whether any shared cache is configured |
| RSA-2048 certificate on a high-connection-rate vhost | High per-handshake cost even when resumption works; CPU per connection high | openssl s_client shows cert type; openssl speed rsa2048 ecdsap256 shows the gap |
| No HTTP/2, clients opening parallel connections | Many connections per client, each needing a handshake | Connection count vs request count; check if mod_http2 is loaded |
Quick checks
All read-only, safe to run on a production box.
# 1. Confirm CPU is the bottleneck and it is Apache consuming it
ps -C httpd -o pid,%cpu --sort=-%cpu 2>/dev/null || ps -C apache2 -o pid,%cpu --sort=-%cpu
# 2. New-connection rate: PassiveOpens in /proc/net/snmp counts accepted
# TCP connections (all ports). Sample twice and take the delta.
awk '$1=="Tcp:"{print $6}' /proc/net/snmp
sleep 10
awk '$1=="Tcp:"{print $6}' /proc/net/snmp
# Note: the delta of established counts (ss -tn state established) is NOT a
# new-connection rate, because closed connections vanish from the list.
# 3. Test session resumption directly against the server
openssl s_client -connect localhost:443 -reconnect 2>/dev/null | grep -c "Reused"
# Expect 5 (six connections, five reused). 0 means resumption is fully broken.
# 4. Check the configured session cache
grep -rEi 'SSLSessionCache|SSLSessionTickets' /etc/httpd/ /etc/apache2/ 2>/dev/null
# 5. Verify the shmcb provider module is loaded
apachectl -M 2>/dev/null | grep socache
# 6. Look for the cache-provider error and OCSP stapling errors
grep -E "shmcb|AH01929|AH02217" /var/log/apache2/error.log /var/log/httpd/error_log 2>/dev/null | tail
# 7. See what certificate type clients pay for
echo | openssl s_client -connect localhost:443 -servername $(hostname) 2>/dev/null | \
openssl x509 -noout -text | grep "Public Key Algorithm"
Check 3 is the decisive one. s_client -reconnect establishes six connections on one session and reports how many were reused. If it prints 0, resumption is broken end to end, regardless of what the config says.
How to diagnose it
Correlate CPU with new connections, not requests. Pull CPU and the new-connection rate to :443 for the same window. If CPU tracks connection rate rather than request rate, handshakes are the load. If CPU tracks request rate instead, look at mod_deflate, mod_rewrite, or mod_security first; see Apache CPU saturation.
Measure resumption from the client’s perspective. Run
openssl s_client -connect localhost:443 -reconnectand count “Reused” lines. Run it again against the public hostname through the load balancer, not just localhost, because the LB may terminate TLS itself or distribute connections across Apache children.Determine which resumption mechanism should be working. If
SSLSessionTickets on(the default), tickets should resume across children. If tickets are off, resumption depends entirely on the server-side cache, which must beshmcbshared memory. Per-child in-memory caching does not survive process boundaries.Check the cache configuration and module. Confirm
mod_socache_shmcbis loaded (it moved to a separate module in 2.4) and thatSSLSessionCache shmcb:/path(size)is set. A missing module on an upgraded config producesSSLSessionCache: 'shmcb' session cache not supported. Loading a newly added module requires a full restart, not a reload.Test the graceful-restart edge case. If you run TLS 1.3 and recently did
apachectl graceful, re-run the-reconnecttest. There is a known behavior where TLS 1.3 resumption silently falls back to full handshakes after a graceful restart with session tickets on; TLS 1.2 is unaffected.Estimate cache sizing. The commonly copied example
shmcb:/path/to/ssl_scache(512000)allocates 512 KiB. On a busy server this turns over fast and effective resumption collapses with no error logged. If resumption degrades at peak but works off-peak, cache size is the suspect.Rule out a post-restart handshake burst. After any crash or hard restart, the session cache is empty and you get a transient burst of full handshakes. That is normal cold-start behavior. It is a problem only if CPU stays high after the cache should have warmed.
Metrics and signals to monitor
| Signal | Why it matters | Warning sign |
|---|---|---|
| Session resumption rate (cache hit/miss) | The direct measure of the problem | Below ~80% on a server with repeat visitors |
| New-connection rate to :443 | New connections are potential full handshakes | Rising without a matching request-rate rise |
| Apache CPU per core | Full handshakes are the top CPU consumer | Sustained >80% at moderate traffic |
| CPU per request | Distinguishes per-connection cost from per-request cost | Rising when connection rate rises, flat when request rate rises |
| Connections vs requests ratio | HTTP/2 multiplexing lowers handshake count | Many connections per request (HTTP/1.1 parallel connections) |
| TTFB on new connections | Handshake time lands in the first byte | Elevated TTFB concentrated on new connections |
| OCSP stapling errors (AH01929, AH02217) | Silent stapling failure adds client-side latency | Any occurrence in the error log |
Apache exposes no native metric for resumption rate. You infer it from the correlation above, or measure it periodically with openssl s_client -reconnect. That gap is why this failure goes undiagnosed for so long.
Fixes
Enable and size the shared session cache
Load mod_socache_shmcb and configure a shared-memory cache, sized for your concurrent session count rather than the 512 KiB example from the docs:
LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
SSLSessionCache shmcb:/var/run/httpd/ssl_scache(10485760)
SSLSessionCacheTimeout 300
The path differs by distribution. A full restart (not graceful) is required after loading the module, so schedule it: a restart drops in-flight connections and briefly empties the session cache. SSLSessionCacheTimeout defaults to 300 seconds; note the timeout is only checked when a session is presented, so stale entries are never purged in the background. Raise the timeout if clients typically return within a longer window, but size the cache so it does not fill first.
Deal with the TLS 1.3 graceful-restart bug
If resumption dies after every graceful restart and you serve TLS 1.3, the documented workaround is SSLSessionTickets off, which switches OpenSSL to stateful (session ID) resumption backed by the shmcb cache. Tradeoff: tickets are off, so resumption now depends entirely on your shared cache being enabled and sized correctly. Do not set tickets off without configuring shmcb, or resumption gets worse.
Prefer ECDSA certificates
ECDSA P-256 signing is roughly an order of magnitude faster than RSA-2048 per operation, which directly cuts the CPU cost of every full handshake. Measure the gap on your own hardware:
# Compare signing performance on this host
openssl speed rsa2048 ecdsap256
Tradeoff: older clients may lack ECDSA support, so dual RSA+ECDSA certificate deployment is common during transition. Even with resumption fixed, ECDSA shrinks the residual cost of the handshakes that cannot be resumed (first visits, cache misses, post-restart bursts).
Reduce handshake count with HTTP/2
HTTP/2 multiplexes many requests over one connection, which directly reduces the number of handshakes your clients need. Requirements: mod_http2 and the event MPM. Enabling it under mpm_prefork fails with AH10034: The mpm module (prefork.c) is not supported by mod_http2, which usually means migrating from mod_php to PHP-FPM first. This does not fix broken resumption, but it lowers the connection rate, which lowers the price of any resumption gap.
Rotate ticket keys deliberately
Apache generates one random ticket key at startup and keeps it for the life of the process. There is no automatic rotation, which weakens forward secrecy for resumed sessions. The practical approaches are periodic restarts (daily, via cron) or SSLSessionTicketKeyFile pointing at a 48-byte random key file that you replace on a schedule, with a restart to pick up the new key. Key rotation via restart empties the shmcb cache as a side effect, so expect a brief full-handshake burst afterward.
Prevention
- Set SSLSessionCache explicitly. The default is
none. Any HTTPS server with repeat clients should have a sized shmcb cache in the base config. - Alert on resumption rate. A periodic
openssl s_client -reconnectprobe from an external host catches regressions that no log line ever will. - Graph CPU against new-connection rate. The divergence between request-driven and connection-driven CPU is the earliest warning.
- Test resumption in deploy pipelines. Run the
-reconnectprobe after every config change or restart that touches mod_ssl, including graceful restarts. - Keep Apache current. TLS session handling has had real security fixes, including CVE-2025-23048 (access control bypass via session resumption, fixed in 2.4.64) and CVE-2024-47252 (mod_ssl log injection). Resumption bugs are not only a performance concern.
How Netdata helps
- Per-second CPU per process, so handshake-driven load is visible as a sharp ramp correlated with traffic events rather than a five-minute average that hides it.
- New-connection and established-connection counts on :443, letting you overlay connection rate against CPU and see immediately whether load tracks connections or requests.
- Connections-per-request visibility that shows whether HTTP/2 multiplexing is actually reducing connection churn.
- Certificate expiry monitoring, so the certificate side of TLS (including renewals that restart Apache and empty the session cache) is watched alongside performance.
- Restart and uptime tracking, which explains post-restart full-handshake bursts when the cache empties.
- Correlating these signals on one dashboard shortens the path from “CPU is high” to “resumption rate collapsed at 14:02, right after the graceful restart.”
Netdata’s Apache HTTP Server monitoring with Netdata brings these signals together with per-second metrics and ML anomaly detection.
Related guides
- Apache 500 Internal Server Error: modules, handlers, and misconfiguration
- Apache 502 Bad Gateway: a backend that returned an invalid response
- Apache 503 Service Unavailable: worker exhaustion versus proxy pool exhaustion
- Apache 504 Gateway Timeout: slow backends, ProxyTimeout, and worker pile-up
- Apache 5xx error rate: 500 vs 502 vs 503 vs 504 and what each one means
- Apache AH00558: Could not reliably determine the server’s fully qualified domain name
- Apache backend response time: telling ‘Apache is slow’ from ’the backend is slow’
- Apache balancer member in error state: reading balancer-manager and failover
- Apache BusyWorkers and IdleWorkers: reading worker utilization from mod_status
- Apache CLOSE_WAIT and TIME_WAIT: connection leaks versus normal churn
- Apache CPU saturation: TLS handshakes, mod_deflate, mod_rewrite, and mod_security
- Apache error log monitoring: severity levels, AH codes, and what to alert on






