Problems installing Ghost in a subdirectory with Nginx (Getting 404)

I’m a beginner using a reverse proxy or managing technical infrastructure. I have my main app (react + nginx) on a server and I’m trying to install Ghost on a subfolder TinyMentions Blog on the same server as my main app.

Unfortunately, I’m not able to successfully configure Nginx to properly serve my Ghost blog.
When I visit the URL ttps://tinymentions.io/blog I get an error: “HTTP/1.1 404 NOT FOUND”

I have searched this forum, Stack Overflow and all over Google, tried different approaches and unfortunately, I’m still not able to come up with a solution for this problem. Every time I tried a new approach, I restarted nginx with sudo service nginx restart but with no luck.

I would appreciate any help with this. If I’m not explaining myself correctly and/or need more information, please let me know.

- What’s your URL?
My main app is on https://tinymentions.io/

- Ghost URL

- What version of Ghost are you using?
Ghost-CLI version: 1.15.0
Ghost version: 3.35.5

- What configuration?
This is my NGINX config file:

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

    server_name tinymentions.io www.tinymentions.io;

    client_max_body_size 4G;

    root /var/www/tiny-mentions/build;
    index index.html;

    location / {
        try_files $uri $uri/ @proxy_to_app;
    }

    location /blog {
        proxy_http_version 1.1;
        proxy_set_header Update &http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_pass http://localhost:2368;
    }

    location @proxy_to_app {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_set_header Host $http_host;
      proxy_connect_timeout 25000s;
      proxy_read_timeout 25000;

      if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain; charset=utf-8';
        add_header 'Content-Length' 0;
        return 204;
     }

     if ($request_method = 'POST') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
     }

     if ($request_method = 'GET') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
     }

      proxy_redirect off;
      proxy_pass http://localhost:5000;
    }

    ssl_certificate /etc/letsencrypt/live/tinymentions.io/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/tinymentions.io/privkey.pem; # managed by Certbot

}

server {
    if ($host = www.tinymentions.io) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    if ($host = tinymentions.io) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    listen [::]:80;

    server_name tinymentions.io www.tinymentions.io;

    return 301 https://$server_name$request_uri;

}

The following is my config.production.json file:

{
  "url": "http://tinymentions.io/blog",
  "server": {
    "port": 2368,
    "host": "127.0.0.1"
  },
  "database": {
    "client": "mysql",
    "connection": {
      "host": "localhost",
      "user": "ghost-917",
      "password": ",`^4d!iCBmI4f6Vqc~TR",
      "database": "ghost_prod"
    }
  },
  "mail": {
    "transport": "Direct"
  },
  "logging": {
    "transports": [
      "file",
      "stdout"
    ]
  },
  "process": "systemd",
  "paths": {
    "contentPath": "/var/www/ghost/content"
  }
}

If you’re using SSL, your Ghost URL must be configured to https - can you try updating that, restarting ghost and seeing if the problem persists?

Hi there, some time ago I also tried to install Ghost on Nginx in a subdirectory but without success, this was my solution: Installed ghost on nginx in subfolder and I got 403 error

Hey Vikas, thanks for replying! I tried in the past to configure the Ghost URL to https, and it didn’t work. I tried again, just to be sure, and it still isn’t working unfortunately.

Hi Ivo, thanks for replying. Unfortunately, I want to avoid using a subdirectory at all costs. If I cannot set up Ghost using a subfolder, I prefer looking at an alternative rather than using a subdomain.

It looks like you’re missing the x-forwarded-proto header (similar to how your app proxy has one)

Here’s the template used by Ghost CLI for reference

Hey Vikas! Thanks for your help! I tried adding the x-forwarded-proto header and restarted nginx with sudo service nginx restart and I’m still getting 404.

This is the code for location /blog in my NGINX config file:

location /blog {
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_http_version 1.1;
    proxy_set_header Update &http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
    proxy_pass http://localhost:2368;
}

Do you know what I’m doing wrong?

Thanks again for the help!

Everything’s working for me :confused:

The rich card below shows that web crawlers are able to visit the URL (linked to https://tinymentions.io/blog/welcome/)

https://tinymentions.io/blog/welcome/

p.s. sudo nginx -s reload is much safer than sudo service nginx restart since the former will not stop the server while the latter will.

1 Like

Hey Vikas! Finally, I was able to fix this issue last night :raised_hands:

It seems that nginx wasn’t restarting correctly so I had to stop nginx with the command sudo systemctl stop nginx and start it again using sudo systemctl start nginx to try different configurations and see if those finally worked.

The location /blog configuration on my NGINX config file that worked is the following:

    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_http_version 1.1;
    proxy_set_header Update &http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
    proxy_pass http://localhost:2368;
}

And the following is my config.production.json file that worked:

{
  "url": "https://tinymentions.io/blog",
  "server": {
    "port": 2368,
    "host": "127.0.0.1"
  },
  "database": {
    "client": "mysql",
    "connection": {
      "host": "localhost",
      "user": "EDITED",
      "password": "EDITED",
      "database": "EDITED"
    }
  },
  "mail": {
    "transport": "Direct"
  },
  "logging": {
    "transports": [
      "file",
      "stdout"
    ]
  },
  "process": "systemd",
  "paths": {
    "contentPath": "EDITED"
  }
}

Thanks for your help solving this! I really appreciate it!

1 Like

By the way, is it possible to edit the original post? I want to remove some critical credentials but I think I am no longer able to edit it.

Once it’s out in the open, you should change the credentials :slight_smile: Discourse usually keeps track of changes so you could edit the original post, but the change would still be tracked

Make sense! Just changed the credentials :slightly_smiling_face: Thanks again for all the help!

1 Like