Skip to content
ohsudo, Home
Networking

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.

Beginner3 min readApplies to Nginx 1.18+ · Ghost 5+ · Ubuntu · Debian · Docker

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:

Terminal window
cd /var/www/ghost
ghost config url https://example.com
ghost restart

Or 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

Terminal window
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 -t
sudo systemctl reload nginx
curl -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.

Tools

Try it yourself

Articles

Related articles