502 Bad Gateway Behind Cloudflare
Symptoms
- Cloudflare's branded error page appears with "Error 502 Bad Gateway" and a Cloudflare Ray ID in the footer
- Visiting the site with Cloudflare bypassed (via `/etc/hosts` pointing directly to origin IP) returns a normal response
- Cloudflare dashboard shows elevated "502" counts in the Analytics → Cache → Errors panel
- curl with `--resolve` to the Cloudflare edge IP returns 502 while direct origin curl succeeds
- Origin Nginx error log shows `SSL_do_handshake() failed` or `peer closed connection in SSL handshake` when Full (Strict) SSL is misconfigured
- Visiting the site with Cloudflare bypassed (via `/etc/hosts` pointing directly to origin IP) returns a normal response
- Cloudflare dashboard shows elevated "502" counts in the Analytics → Cache → Errors panel
- curl with `--resolve` to the Cloudflare edge IP returns 502 while direct origin curl succeeds
- Origin Nginx error log shows `SSL_do_handshake() failed` or `peer closed connection in SSL handshake` when Full (Strict) SSL is misconfigured
Root Causes
- Origin web server is down or not accepting connections on port 80/443 — Cloudflare cannot reach it at all
- Cloudflare SSL mode is set to "Full (Strict)" but the origin has a self-signed or expired certificate, causing TLS handshake failure
- Origin firewall blocks Cloudflare's IP ranges — requests from Cloudflare edge nodes are dropped
- Origin server returns an invalid HTTP response (e.g. sends a raw TCP reset or malformed HTTP/1.x headers) that Cloudflare cannot proxy
- Cloudflare Tunnel (`cloudflared`) daemon crashed or lost its connection to the Cloudflare edge network
Diagnosis
**Step 1: Check if the issue is on Cloudflare or the origin**
```bash
# Bypass Cloudflare by curling origin IP directly
curl -v --resolve yourdomain.com:80:YOUR_ORIGIN_IP http://yourdomain.com/
# If this succeeds → Cloudflare cannot reach origin
# If this also fails → origin itself is down
```
**Step 2: Check Cloudflare SSL/TLS settings**
- Dashboard → SSL/TLS → Overview → confirm mode is 'Flexible', 'Full', or 'Full (Strict)'
- If origin has a self-signed cert, switch to 'Full' (not Strict) or install a valid cert with `certbot`
**Step 3: Verify Cloudflare IPs are not blocked on origin**
```bash
# List Cloudflare IPv4 ranges
curl https://www.cloudflare.com/ips-v4
# Check iptables
sudo iptables -L INPUT -n | grep DROP
```
**Step 4: Check cloudflared tunnel status (if using Tunnel)**
```bash
sudo systemctl status cloudflared
sudo journalctl -u cloudflared -n 50 --no-pager
cloudflared tunnel list # check CONNECTIONS column
```
**Step 5: Inspect origin server logs**
```bash
sudo tail -n 100 /var/log/nginx/error.log
sudo journalctl -u gunicorn --since '10 min ago'
```
```bash
# Bypass Cloudflare by curling origin IP directly
curl -v --resolve yourdomain.com:80:YOUR_ORIGIN_IP http://yourdomain.com/
# If this succeeds → Cloudflare cannot reach origin
# If this also fails → origin itself is down
```
**Step 2: Check Cloudflare SSL/TLS settings**
- Dashboard → SSL/TLS → Overview → confirm mode is 'Flexible', 'Full', or 'Full (Strict)'
- If origin has a self-signed cert, switch to 'Full' (not Strict) or install a valid cert with `certbot`
**Step 3: Verify Cloudflare IPs are not blocked on origin**
```bash
# List Cloudflare IPv4 ranges
curl https://www.cloudflare.com/ips-v4
# Check iptables
sudo iptables -L INPUT -n | grep DROP
```
**Step 4: Check cloudflared tunnel status (if using Tunnel)**
```bash
sudo systemctl status cloudflared
sudo journalctl -u cloudflared -n 50 --no-pager
cloudflared tunnel list # check CONNECTIONS column
```
**Step 5: Inspect origin server logs**
```bash
sudo tail -n 100 /var/log/nginx/error.log
sudo journalctl -u gunicorn --since '10 min ago'
```
Resolution
**Fix 1: Restart a crashed cloudflared tunnel**
```bash
sudo systemctl restart cloudflared
sudo systemctl enable cloudflared
cloudflared tunnel list # confirm connections appear
```
**Fix 2: Fix SSL mode mismatch**
- In Cloudflare Dashboard → SSL/TLS → set mode to **Full** if origin uses a self-signed cert, or **Full (Strict)** only after installing a valid cert:
```bash
sudo certbot --nginx -d yourdomain.com
```
**Fix 3: Allowlist Cloudflare IPs on origin firewall**
```bash
# Allow all Cloudflare ranges on port 80/443
for ip in $(curl -s https://www.cloudflare.com/ips-v4); do
sudo ufw allow from $ip to any port 80,443 proto tcp
done
```
**Fix 4: Restart the origin application**
```bash
sudo systemctl restart nginx gunicorn
curl -v http://YOUR_ORIGIN_IP/ # confirm 200
```
```bash
sudo systemctl restart cloudflared
sudo systemctl enable cloudflared
cloudflared tunnel list # confirm connections appear
```
**Fix 2: Fix SSL mode mismatch**
- In Cloudflare Dashboard → SSL/TLS → set mode to **Full** if origin uses a self-signed cert, or **Full (Strict)** only after installing a valid cert:
```bash
sudo certbot --nginx -d yourdomain.com
```
**Fix 3: Allowlist Cloudflare IPs on origin firewall**
```bash
# Allow all Cloudflare ranges on port 80/443
for ip in $(curl -s https://www.cloudflare.com/ips-v4); do
sudo ufw allow from $ip to any port 80,443 proto tcp
done
```
**Fix 4: Restart the origin application**
```bash
sudo systemctl restart nginx gunicorn
curl -v http://YOUR_ORIGIN_IP/ # confirm 200
```
Prevention
- **Monitor Cloudflare Health Alerts**: Enable "Origin Error Rate" notifications in Cloudflare Dashboard → Notifications
- **Use Cloudflare Tunnel** instead of exposing origin IP — tunnel connections are outbound only, eliminating firewall allowlisting
- **Automate cert renewal**: `certbot renew` in cron or systemd timer prevents surprise SSL expiry under Full (Strict) mode
- **Cloudflare Page Rules**: Add a "Cache Level: Bypass" rule for API paths so Cloudflare always proxies and you see real error rates
- **Origin Health Checks**: Use Cloudflare Load Balancing health checks or UptimeRobot targeting origin IP directly
- **Use Cloudflare Tunnel** instead of exposing origin IP — tunnel connections are outbound only, eliminating firewall allowlisting
- **Automate cert renewal**: `certbot renew` in cron or systemd timer prevents surprise SSL expiry under Full (Strict) mode
- **Cloudflare Page Rules**: Add a "Cache Level: Bypass" rule for API paths so Cloudflare always proxies and you see real error rates
- **Origin Health Checks**: Use Cloudflare Load Balancing health checks or UptimeRobot targeting origin IP directly