Nginx reverse proxy for Ghost: HTTPS, the url setting and redirect loops
Put Nginx in front of a Ghost blog: a complete HTTPS config, the url setting, X-Forwarded-Proto to avoid redirect loops, upload limits, Docker Compose, and common errors.
by OhSudo ·
Ghost listens on port 2368, and it’s designed to have a proxy in front. There’s only one thing to get right: Ghost must know its public address, and that the visitor came over HTTPS. Otherwise, you get a redirect loop or broken links.
The Nginx Config Generator with these options writes this for your domain.
The config
server { listen 80; listen [::]:80; server_name example.com; return 301 https://example.com$request_uri;}
server { listen 443 ssl; listen [::]:443 ssl; server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3;
# Themes and images are uploaded through this, so the default 1 MB isn't enough. client_max_body_size 50m;
location / { proxy_pass http://127.0.0.1:2368; proxy_set_header Host $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; }}1. Tell Ghost its address
With Ghost-CLI, from the install folder:
cd /var/www/ghostghost config url https://example.comghost restartOr in config.production.json:
{ "url": "https://example.com", "server": { "host": "127.0.0.1", "port": 2368 }}url has to be exactly what visitors use, including https://. 127.0.0.1 keeps Ghost itself off the internet.
Ghost-CLI can do all of this for you. ghost setup nginx ssl writes an Nginx config and gets a certificate. The manual config above is for when you’re using your own setup, or you want to see what’s going on.
2. Get HTTPS, and turn it on
sudo certbot certonly --standalone -d example.com \ --pre-hook "systemctl stop nginx" --post-hook "systemctl start nginx"sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/sudo nginx -tsudo systemctl reload nginxcurl -I https://example.com/If it fails, see Certbot failed to authenticate.
3. With Docker Compose
services: ghost: image: ghost:5-alpine restart: unless-stopped ports: - "127.0.0.1:2368:2368" environment: url: https://example.com database__client: mysql database__connection__host: db database__connection__user: ghost database__connection__password: ${DB_PASSWORD} database__connection__database: ghost volumes: - ghost:/var/lib/ghost/content depends_on: db: condition: service_healthy
db: image: mysql:8.4 restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} MYSQL_DATABASE: ghost MYSQL_USER: ghost MYSQL_PASSWORD: ${DB_PASSWORD} healthcheck: test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -u root -p$$MYSQL_ROOT_PASSWORD --silent"] interval: 10s retries: 10 volumes: - db:/var/lib/mysql
volumes: ghost: db:Keep passwords in a .env file that git ignores. Ghost’s default in Docker is development mode, using SQLite, unless you set the database as above. See the Docker Compose Generator for other services, and Nginx with Docker Compose for more on this setup.
4. Behind Cloudflare
With Cloudflare’s proxy on, use Full (strict) for SSL mode. Flexible makes Cloudflare talk to your server over plain HTTP, and then Ghost’s redirect to HTTPS loops forever. The cure is in Too many redirects.
Also, tell Cloudflare not to cache /ghost/, or you can end up looking at someone else’s admin page, or a stale login.
When it goes wrong
ERR_TOO_MANY_REDIRECTS. Ghost’s url says https://, and it thinks the request was plain HTTP. Check that the line proxy_set_header X-Forwarded-Proto $scheme; is there. If there’s another proxy in front, Nginx’s $scheme is http, so pass on that proxy’s value. More in Too many redirects.
Links or images point at localhost:2368, or the old domain. The url setting is wrong. Fix it, and restart Ghost.
413 Request Entity Too Large when uploading a theme or an image. Raise client_max_body_size: see Nginx 413.
502 Bad Gateway. Ghost isn’t running, which you can check with ghost status or ghost log, or it’s on another port: see Nginx 502.
Ghost-CLI says Ghost is running on an unsupported node version. Each Ghost release supports certain versions of Node.js, so check Ghost’s documentation before upgrading Node.js.
The Ghost admin at /ghost gives a 404, or won’t load. Something in front is rewriting or caching that path. Test directly with curl -I http://127.0.0.1:2368/ghost/.
Warning
Create your admin account as soon as the site goes online. The first visitor to /ghost can create the owner account, so don’t leave a fresh install open on the internet for long.
Found this useful? Share it.
Found a mistake? Let us know at contact@ohsudo.com.