How to Fix Nginx OCSP Stapling Failed: ssl_stapling_verify On But No Trusted Certificate Provided
Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 10 mins
TL;DR
- What broke:
ssl_stapling_verify onis set in your Nginx config, but nossl_trusted_certificatedirective exists — Nginx cannot verify the OCSP responder's signature, so stapling silently fails or errors out at startup. - How to fix it: Add
ssl_trusted_certificate /path/to/ca-chain.pem;pointing to your issuing CA's full chain (root + intermediates), and add aresolverdirective so Nginx can reach the OCSP endpoint. - Fast path: Use our Client-Side Sandbox below to auto-refactor this — paste your server block and get a corrected config with the missing directives injected.
The Incident (What Does the Error Mean?)
Raw error from nginx -t or error.log:
nginx: [warn] ssl_stapling_verify: no trusted certificate provided
OCSP stapling failed: ssl_stapling_verify is on but no trusted certificate provided
Or at runtime in /var/log/nginx/error.log:
2024/01/15 03:42:11 [error] 1234#0: OCSP_basic_verify() failed
(SSL: error:27069076:OCSP routines:OCSP_basic_verify:signer certificate not found)
while requesting certificate status, responder: ocsp.sectigo.com
Immediate consequence: Nginx cannot cryptographically verify the OCSP response it fetches from your CA's responder. With ssl_stapling_verify on, Nginx refuses to staple the response rather than staple an unverified one. Clients receive TLS handshakes with no OCSP staple attached — meaning browsers must perform their own live OCSP lookup (adding 100–400ms latency per new connection) or, worse, hard-fail on browsers with Must-Staple enforcement enabled in the certificate.
The Attack Vector / Blast Radius
This is not just a performance degradation — it's a revocation enforcement gap.
Scenario 1 — Silent revocation bypass: Without a verified stapled response, a client browser may skip OCSP validation entirely (soft-fail behavior is the default in Chrome and Firefox). If your certificate is compromised and revoked by your CA, clients connecting to your server will not be notified of the revocation. An attacker holding your stolen private key can continue impersonating your domain.
Scenario 2 — Must-Staple certificate hard failure: If your certificate was issued with the status_request TLS extension (OCSP Must-Staple, RFC 7633), browsers that enforce it will terminate the connection entirely when no valid staple is present. This is a full outage for compliant clients — no warning, no fallback.
Scenario 3 — OCSP responder MITM: Without ssl_stapling_verify, an attacker with network position between your server and the CA's OCSP endpoint could inject a fabricated "good" response for a revoked cert. ssl_stapling_verify on is the correct defense — but it only works when Nginx has the CA chain to verify against.
Blast radius: Every TLS client connecting to this vhost. Severity compounds if the cert has Must-Staple or if your org has compliance requirements (PCI-DSS 4.0 §6.3.3, NIST SP 800-52r2).
How to Fix It (The Solution)
Root Cause
ssl_stapling_verify on instructs Nginx to verify the OCSP response signature using a trusted CA certificate. Nginx uses ssl_trusted_certificate for this — it is separate from ssl_certificate (your leaf cert + chain). If ssl_trusted_certificate is absent, Nginx has no anchor to verify the OCSP responder's signature against.
Step 1 — Build the CA chain PEM
You need a PEM file containing intermediate CA(s) + root CA (not your leaf cert).
# For Let's Encrypt (chain.pem already contains intermediates)
cp /etc/letsencrypt/live/yourdomain.com/chain.pem /etc/nginx/ssl/ocsp-chain.pem
# For commercial CAs (Sectigo, DigiCert, etc.) — concatenate manually:
cat intermediate.crt root.crt > /etc/nginx/ssl/ocsp-chain.pem
# Verify the chain is correct:
openssl verify -CAfile /etc/nginx/ssl/ocsp-chain.pem /etc/nginx/ssl/yourdomain.crt
# Expected: yourdomain.crt: OK
Basic Fix
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /etc/nginx/ssl/yourdomain.crt;
ssl_certificate_key /etc/nginx/ssl/yourdomain.key;
ssl_stapling on;
ssl_stapling_verify on;
+ ssl_trusted_certificate /etc/nginx/ssl/ocsp-chain.pem;
+ resolver 8.8.8.8 8.8.4.4 valid=300s;
+ resolver_timeout 5s;
}
Enterprise Best Practice
In production, do not use public resolvers (8.8.8.8). Use your internal DNS resolvers or a dedicated resolver with DNSSEC validation. Also pin the OCSP chain to a file managed by your secrets/cert pipeline, not manually placed.
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /etc/nginx/ssl/yourdomain_fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/yourdomain.key;
ssl_stapling on;
ssl_stapling_verify on;
- # ssl_trusted_certificate missing — OCSP verify broken
+ ssl_trusted_certificate /etc/nginx/ssl/ocsp-chain.pem;
- # No resolver — Nginx cannot reach OCSP endpoint
+ resolver 10.0.0.2 10.0.0.3 valid=300s ipv6=off;
+ resolver_timeout 10s;
# Harden further: cache stapled responses
+ ssl_session_cache shared:SSL:10m;
+ ssl_session_timeout 1d;
+ ssl_session_tickets off;
}
After applying:
nginx -t && nginx -s reload
# Verify stapling is working:
openssl s_client -connect yourdomain.com:443 -status -tlsextdebug 2>&1 \
| grep -A 10 'OCSP response'
# Look for: OCSP Response Status: successful (0x0)
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. Nginx config linting in your pipeline:
# Use gixy — Nginx static security analyzer
pip install gixy
gixy /etc/nginx/nginx.conf
# Flags missing ssl_trusted_certificate when ssl_stapling_verify is on
2. Checkov IaC scan (if Nginx config is rendered from Terraform/Ansible):
# .checkov.yml
checks:
- CKV_NGINX_5 # Ensure ssl_stapling is enabled
- CKV_NGINX_6 # Ensure ssl_trusted_certificate is present with stapling_verify
3. GitHub Actions gate — fail the deploy if OCSP staple is missing post-deploy:
- name: Verify OCSP Stapling
run: |
RESPONSE=$(echo | openssl s_client \
-connect ${{ secrets.DOMAIN }}:443 \
-status 2>&1 | grep 'OCSP Response Status')
echo "$RESPONSE"
echo "$RESPONSE" | grep -q 'successful' || \
(echo '❌ OCSP stapling not working' && exit 1)
4. Cert rotation automation: Use Certbot with --deploy-hook or cert-manager (Kubernetes) to automatically rebuild the ocsp-chain.pem on every renewal. Manual chain files are the #1 cause of this error recurring after cert rotation.
# /etc/letsencrypt/renewal-hooks/deploy/rebuild-ocsp-chain.sh
#!/bin/bash
cp /etc/letsencrypt/live/${RENEWED_DOMAINS}/chain.pem \
/etc/nginx/ssl/ocsp-chain.pem
nginx -s reload