Fixing Infinite 301 Redirects: Ghost Docker with Cloudflare HTTPS
Fixing Infinite 301 Redirects: Ghost Docker with Cloudflare HTTPS
Introduction
Running Ghost on Docker with Cloudflare as your DNS and CDN provider can significantly improve performance and security. However, many users encounter a frustrating issue: an infinite 301 redirect loop when accessing their blog through HTTPS. If you're stuck in this redirect loop, you're not alone. This comprehensive guide will walk you through understanding the root cause and implementing the most effective solutions.
Understanding the Problem
What Causes the Infinite 301 Redirect Loop?
The infinite 301 redirect loop typically occurs due to a mismatch between how Cloudflare and Ghost handle HTTPS connections. Let's break down the two primary causes:
Cause 1: Cloudflare's "Flexible SSL" Mode
Cloudflare's default SSL/TLS encryption mode is "Flexible," which operates as follows:
- Connections between your browser and Cloudflare are encrypted with HTTPS
- Connections between Cloudflare and your origin server (where Ghost runs) are unencrypted HTTP
When Ghost is configured to use HTTPS (by setting url: https://your-domain.com), it automatically redirects all HTTP requests to HTTPS. This creates a problematic cycle:
- Browser requests
https://your-domain.com - Cloudflare receives the HTTPS request and forwards it to Ghost as HTTP
- Ghost sees an HTTP request and redirects to HTTPS (status 301)
- Cloudflare receives the redirect response
- The cycle repeats indefinitely
Cause 2: Missing Reverse Proxy Headers
When Ghost runs behind a reverse proxy (Nginx, Nginx Proxy Manager, etc.), it needs to receive specific HTTP headers to understand that the original request was secure (HTTPS). Without these headers, Ghost thinks the request is insecure and redirects accordingly.
Solution 1: Modify Cloudflare SSL/TLS Settings (Recommended)
This is the quickest and most effective solution for most users. Follow these steps:
Step-by-Step Instructions
- Log in to your Cloudflare dashboard
- Select your domain from the list
- Navigate to SSL/TLS in the left sidebar menu
- Find the "Encryption mode" section at the top
- Change from "Flexible" to "Full (Strict)"
Why This Works
The "Full (Strict)" mode ensures that:
- Connections from browsers to Cloudflare are HTTPS encrypted
- Connections from Cloudflare to your origin server are also HTTPS encrypted
- Ghost receives the request as HTTPS and doesn't need to perform a redirect
This configuration respects your SSL certificate on the origin server and eliminates the protocol mismatch that causes the redirect loop.
Important Considerations
Before changing to "Full (Strict)" mode, ensure that:
- Your Ghost instance has a valid SSL certificate (Let's Encrypt works great with Docker)
- Your reverse proxy is properly configured with SSL certificates
- Your DNS records point correctly to Cloudflare
Solution 2: Configure Nginx Proxy Manager Headers
If you're using Nginx Proxy Manager as your reverse proxy, this solution will resolve the issue while maintaining flexibility:
Step-by-Step Instructions
- Open your Nginx Proxy Manager dashboard
- Select your Ghost host from the list
- Click the "Advanced" tab
- Add the following configuration in the Custom Nginx Configuration field:
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 https;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Port 443;
- Make sure you have an SSL certificate configured in the SSL tab
- Enable "Force SSL" to ensure all traffic is encrypted
- Save your configuration
Why This Works
The key header is X-Forwarded-Proto https;. This tells Ghost that the original request was HTTPS, preventing it from issuing a redirect. The other headers ensure that Ghost receives correct information about the client's IP address and the original request details.
Solution 3: Configure Nginx Directly
If you're running Nginx directly (not through Nginx Proxy Manager), add these headers to your Nginx configuration:
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /path/to/your/certificate.crt;
ssl_certificate_key /path/to/your/private.key;
# SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://127.0.0.1:2368;
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_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Port $server_port;
}
}
Don't forget the redirect from HTTP to HTTPS:
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
Solution 4: Verify Your Docker Compose Configuration
Ensure your Docker Compose file is correctly configured:
version: '3.8'
services:
ghost:
image: ghost:latest
container_name: ghost_blog
ports:
- "2368:2368"
environment:
url: https://your-domain.com
NODE_ENV: production
database__client: mysql
database__connection__host: mysql_service
database__connection__user: ghost_user
database__connection__password: your_secure_password
database__connection__database: ghost_db
depends_on:
- mysql_service
networks:
- ghost_network
restart: unless-stopped
mysql_service:
image: mysql:8
container_name: ghost_mysql
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: ghost_db
MYSQL_USER: ghost_user
MYSQL_PASSWORD: your_secure_password
networks:
- ghost_network
volumes:
- mysql_/var/lib/mysql
restart: unless-stopped
networks:
ghost_network:
driver: bridge
volumes:
mysql_
Critical Settings:
- The
urlparameter must use HTTPS:https://your-domain.com - Ensure your Ghost container and reverse proxy are on the same Docker network
- The port mapping should align with your reverse proxy configuration
Troubleshooting Guide
If you've applied one or more solutions and still experience redirect loops, follow these diagnostic steps:
Check Ghost Logs
docker logs ghost_container_name
Look for error messages related to redirects or protocol mismatches.
Test with cURL
curl -I https://your-domain.com
This shows the response headers. A 301 response indicates the redirect is still occurring.
curl -I -H "X-Forwarded-Proto: https" http://localhost:2368
This tests how Ghost responds when it receives the HTTPS header.
Verify Cloudflare Settings
- Check that you've actually saved the SSL/TLS mode change
- Wait a few minutes for changes to propagate (usually instant)
- Clear your browser cache or use incognito mode when testing
Examine Reverse Proxy Configuration
- Verify that your proxy_pass directive points to the correct Ghost port (default: 2368)
- Ensure all X-Forwarded headers are present
- Check that SSL certificates are valid and not expired
Check DNS Resolution
nslookup your-domain.com
Confirm that your domain resolves to Cloudflare's nameservers.
Best Practices for Ghost and Cloudflare Integration
Once you've resolved the redirect issue, follow these best practices:
1. Use Full (Strict) SSL Mode
"Full (Strict)" provides the best security posture, ensuring end-to-end encryption. This is recommended for production environments.
2. Enable Additional Cloudflare Security Features
- Always Use HTTPS: Forces all HTTP traffic to HTTPS
- Minimum TLS Version: Set to TLS 1.2 or higher
- HSTS (HTTP Strict Transport Security): Adds an extra layer of security
3. Implement Proper Logging
Monitor your Ghost application logs regularly for any unusual redirect patterns or errors. Most issues surface in the logs before they become critical.
4. Regular Certificate Maintenance
If using Let's Encrypt with Docker, ensure your certificate renewal is automated. Most Docker images handle this automatically, but verify it's working.
5. Test After Updates
After updating Ghost or changing your infrastructure, test your HTTPS connection thoroughly to ensure nothing has regressed.
Conclusion
The infinite 301 redirect loop is a common issue when running Ghost on Docker with Cloudflare, but it's entirely preventable with proper configuration. The easiest solution is to change Cloudflare's SSL/TLS mode from "Flexible" to "Full (Strict)," which resolves the issue for most users instantly.
If that doesn't work for your setup, properly configuring your reverse proxy with the correct X-Forwarded headers will eliminate the problem. By understanding the root cause—the protocol mismatch between your CDN and origin server—you can troubleshoot similar issues in the future.
Your Ghost blog deserves a smooth, secure, and fast experience. With these solutions in place, your readers will enjoy uninterrupted access to your content without any redirect headaches.
Have you encountered this issue? Which solution worked for you? Share your experience in the comments below!