Independent, External Nginx Proxy 502 Bad Gateway

Hello!

I’m running Ghost in a VM on Proxmox (Ubuntu 22.04.x) LTS as well as an additional VM dedicated to Nginx (also Ubuntu 22.04.x LTS). The two VM’s are on the same subnet and VLAN. Nginx VM is able to ping Ghost and vice versa.

PING 10.0.1.105 (10.0.1.105) 56(84) bytes of data.
64 bytes from 10.0.1.105: icmp_seq=1 ttl=64 time=0.303 ms
64 bytes from 10.0.1.105: icmp_seq=2 ttl=64 time=0.526 ms
64 bytes from 10.0.1.105: icmp_seq=3 ttl=64 time=0.526 ms
64 bytes from 10.0.1.105: icmp_seq=4 ttl=64 time=0.559 ms
64 bytes from 10.0.1.105: icmp_seq=5 ttl=64 time=0.519 ms
64 bytes from 10.0.1.105: icmp_seq=6 ttl=64 time=0.221 ms
64 bytes from 10.0.1.105: icmp_seq=7 ttl=64 time=0.406 ms
64 bytes from 10.0.1.105: icmp_seq=8 ttl=64 time=0.753 ms
64 bytes from 10.0.1.105: icmp_seq=9 ttl=64 time=0.438 ms
64 bytes from 10.0.1.105: icmp_seq=10 ttl=64 time=0.504 ms
64 bytes from 10.0.1.105: icmp_seq=11 ttl=64 time=0.490 ms
64 bytes from 10.0.1.105: icmp_seq=12 ttl=64 time=0.395 ms
64 bytes from 10.0.1.105: icmp_seq=13 ttl=64 time=0.436 ms
64 bytes from 10.0.1.105: icmp_seq=14 ttl=64 time=0.480 ms
^C
--- 10.0.1.105 ping statistics ---
14 packets transmitted, 14 received, 0% packet loss, time 13301ms
rtt min/avg/max/mdev = 0.221/0.468/0.753/0.119 ms

In addition, I have verified that Ghost is installed and running by posting the results of ghost status to the terminal.

running (production)

Output of netstat -tulpn shows the node app running on the loopback:

Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      721/sshd: /usr/sbin 
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      652/systemd-resolve 
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      2341/mysqld         
tcp        0      0 127.0.0.1:2368          0.0.0.0:*               LISTEN      3626/node           
tcp        0      0 127.0.0.1:33060         0.0.0.0:*               LISTEN      2341/mysqld         
tcp6       0      0 :::22                   :::*                    LISTEN      721/sshd: /usr/sbin 
udp        0      0 127.0.0.53:53           0.0.0.0:*                           652/systemd-resolve

Here’s my config.production.json:

{
  "url": "https://www.domain.io",
  "server": {
    "port": 2368,
    "host": "127.0.0.1"
  },
  "database": {
    "client": "mysql",
    "connection": {
      "host": "127.0.0.1",
      "user": "ghost-126",
      "password": "<some_password>",
      "database": "domain_prod"
    }
  },
  "mail": {
    "transport": "Direct"
  },
  "logging": {
    "transports": [
      "file",
      "stdout"
    ]
  },
  "process": "systemd",
  "paths": {
    "contentPath": "/var/www/domain/content"
  }
}

On the reverse proxy side of the equation my server config has been tailored after this reference from Github. I must have read every single topic in the forums on this subject and none of the solutions have worked for me. The Nginx server hosts several sites without any issues whatsoever, it also hands out LetsEncrypt certs via Certbot automations (renewed every 90 days).

Here’s the conf file with server blocks - I have it set up so that if a user hits port 80 it automatically re-routes traffic back to 443:

server {
    listen  443 ssl  http2;
    listen [::]:443 ssl http2;

    server_name www.domain.io;

    ssl_certificate /etc/letsencrypt/live/www.domain.io/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/www.domain.io/privkey.pem;

    location / {
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
        proxy_pass http://10.0.1.105:2368;

    }

    client_max_body_size 1g;
}

server {
    if ($host = www.domain.io) {
        return 301 https://$host$request_uri;
    }

    listen 80;
    listen [::]:80;

    server_name www.domain.io;
    return 404;

}

UFW is up on the Nginx reverse proxy server and down (inactive) on the Ghost server - firewall is not the issue. I’m not sure what I’m doing wrong. I briefly had almost the exact same setup about three years ago and it was a breeze to get working. For some reason revisiting this in 2025 is causing me major issues.

Hopefully the above is enough info to get some helpful feedback or better yet a suggested solution! :grinning:

Please let me know if any additional info will help diagnose, I’d be happy to provide.

Sorry, forgot to include these refs:

  • For initial setup and config - link
  • For troubleshooting of issue (most helpful so far) - link

I might have misread, but if Ghost is bound to 127.0.01, it won’t respond to requests targeting 10.0.1.105, right?

(btw, thanks for including all the detail!)

2 Likes

Thanks for your reply, @vikaspotluri123

Ghost is running on 10.0.1.105 and my nginx server is 10.0.1.200

If it’s running on 127.0.0.1 is that not equivalent to localhost and/or 0.0.0.0*?

You were correct, @vikaspotluri123 ! :man_facepalming:

Edited the config.production.json and changed host to Ghost server IP 10.0.1.105 reloaded the page and it worked!

Easy fix. :smiley:

EDIT I made no specific or manual customizations upon install, btw. I let the Ghost Installer do its thing and only entered details when requested. In theory, I did everything correctly. How would I have known that the installer would bind to 127.0.0.1 without inspecting the config.production.json?? I’m wondering if this is a “bug” with the installer or some unforeseen circumstance/edge case that has gone unaccounted for?

2 Likes

It’s equivalent to binding to 127.0.0.1[1], which means only that machine can access it. That’s why nginx wasn’t able to access it!

The CLI is designed for the most common use case, a server accessible by the internet, with a reverse proxy on the same host. In that case, setting the host to e.g. 0.0.0.0 can be problematic because the proxy can be bypassed.


  1. Technically it’s whatever you bind it to in your hosts file, so with ipv6, it could be ::1, or something else if you mess with it :stuck_out_tongue: ↩︎

So, maybe a question upon install that asks if you are using an external proxy (please enter IP), else local install and therefore Nginx instance?

Anecdotally, most people trying to install Ghost with the CLI don’t do this, so adding an extra question that’s more technical would cause more friction, IMO.