Technology

Hosting Ghost Blog and Headless Blogging with Node.js, Reverse Proxy and SSL

If you are planning to host a Ghost blog or use Ghost as a headless blogging platform, you quickly notice it behaves differently from classic PHP-based systems like WordPress. Ghost runs on Node.js, listens on its own port, and expects you to put a reverse proxy and SSL in front of it. When this setup is done properly, you get a very fast, modern publishing stack that can serve a traditional blog, a headless content API, or both at the same time. In this article, we will walk through how we at dchost.com typically design and deploy Ghost: choosing the right server, installing Node.js and Ghost, putting Nginx in front as a reverse proxy, enabling HTTPS with SSL, and integrating Ghost into headless architectures where a separate frontend (for example Next.js or Nuxt) consumes its content API. The goal is to give you a practical, copy‑paste‑friendly blueprint you can reuse on your own VPS or dedicated server.

Why Ghost and Headless Blogging Need a Different Hosting Approach

Ghost is designed as a modern publishing platform with performance and clean content APIs in mind. That comes with a few implications for hosting:

  • It runs on Node.js, not PHP. So traditional shared hosting optimized for PHP/MySQL is usually not a good fit.
  • It listens on an internal port (by default 2368) and expects you to put a web server like Nginx in front as a reverse proxy.
  • It has a built‑in JSON Content API, which makes Ghost perfect as a headless CMS feeding one or more frontends.
  • It benefits a lot from proper SSL and HTTP/2, especially when you start serving images, scripts and multiple frontends.

In our daily work at dchost.com, we see two main Ghost scenarios:

  1. Classic Ghost blog on its own domain like blog.example.com (or as the main site), served directly by Ghost, with Nginx + SSL in front.
  2. Headless Ghost where Ghost only provides the content API and admin panel, while a separate frontend (Next.js, Nuxt, a mobile app, etc.) renders pages, often deployed on the same VPS or on another server.

Both scenarios share the same foundation: a clean Node.js runtime, a process manager or systemd service, Nginx as reverse proxy, and an HTTPS setup that is easy to maintain. If you are new to Node.js hosting, our article hosting Node.js and Express applications on shared hosting vs VPS vs serverless gives useful background on when a VPS or dedicated server is the right choice.

Planning the Server: Resources, OS and Network Layout

Choosing VPS vs dedicated vs colocation

For most Ghost blogs and headless setups, a Linux VPS is the sweet spot: enough control to run Node.js and Nginx, but still easy to manage. At dchost.com we typically recommend:

  • Small personal or company blog: 1 vCPU, 1–2 GB RAM, fast SSD/NVMe storage.
  • Medium‑size publication or agency blog with thousands of visitors per day: 2–4 vCPU, 4–8 GB RAM.
  • Multi‑language headless setup with multiple frontends and heavy API traffic: 4+ vCPU, 8+ GB RAM, often on a larger VPS or a dedicated server.

If you expect very large traffic peaks, a dedicated server or colocation at our data centers lets you scale CPU and storage further, and combine Ghost with additional services (CDN origin, search, analytics) on the same physical machine.

Operating system and base hardening

We usually start with a fresh Ubuntu LTS or Debian image on the VPS or dedicated server. The general first‑day setup is similar to what we describe in our guide on what to do in the first 24 hours on a new VPS:

  • Update system packages and security patches.
  • Create a non‑root user with sudo access.
  • Harden SSH access (key‑based login, non‑standard port if you prefer, disable root login).
  • Enable a simple firewall (ufw or firewalld) with only SSH and HTTP/HTTPS open.

Network and DNS layout

Before installing Ghost, decide how you want your domains and subdomains to look. Common patterns include:

  • Main site on Ghost: example.com points to Ghost (via Nginx reverse proxy).
  • Blog as a subdomain: blog.example.com for Ghost, www.example.com for your main site or app.
  • Headless API subdomain: content.example.com or ghost.example.com serving only the admin panel and JSON API; frontend lives elsewhere.

On your domain registrar, set A (and optionally AAAA) records to the VPS IP. If you are unsure how DNS pieces fit together, our article on how domain, DNS, server and SSL work together gives a practical overview.

Installing Node.js, Ghost-CLI and Running Ghost as a Service

Preparing the system for Ghost

Ghost runs on Node.js and is easiest to manage via the official Ghost‑CLI tool. A typical base setup on Ubuntu/Debian looks like this:

sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx mysql-server

# Optional: secure MySQL quickly
sudo mysql_secure_installation

Ghost officially supports specific Node.js LTS versions. We usually install Node.js from the NodeSource repository or nvm. For a system‑wide installation using NodeSource:

curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs

node -v
npm -v

Creating a directory and system user for Ghost

We like to keep Ghost isolated under /var/www and run it as a dedicated user:

sudo mkdir -p /var/www/ghost
sudo useradd -r -U -d /var/www/ghost ghost
sudo chown ghost:ghost /var/www/ghost

Installing Ghost-CLI and Ghost itself

Ghost‑CLI simplifies installation, upgrades and systemd service management. Install it globally:

sudo npm install -g ghost-cli@latest

Then switch to the Ghost directory as the ghost user and run the installer:

sudo -u ghost -H bash
cd /var/www/ghost

ghost install

Ghost‑CLI will ask a series of questions:

  • Blog URL (e.g. https://blog.example.com)
  • MySQL connection details
  • Whether to set up systemd to manage the service
  • Whether to configure Nginx automatically
  • Whether to set up SSL using Let’s Encrypt

You can let Ghost‑CLI handle Nginx and SSL automatically, or you can choose to skip those parts and configure them manually. In this article we will walk through a manual Nginx and SSL setup so you fully understand what is going on behind the scenes.

After the installer runs, Ghost will typically be running on an internal port (for example 2368) and managed by systemd as a service named something like ghost_blog-example-com. You can check its status with:

sudo systemctl status ghost_blog-example-com

Ghost is now reachable on http://127.0.0.1:2368. The next step is to expose it to the outside world safely via Nginx.

Putting Nginx In Front as a Reverse Proxy

What a reverse proxy does in a Ghost setup

A reverse proxy sits in front of your application server and forwards incoming HTTP/HTTPS requests to it. With Ghost, the typical pattern is:

  • Clients connect to port 80/443 on Nginx.
  • Nginx terminates SSL (for HTTPS), handles HTTP/2, compression, caching headers, and routing.
  • Nginx forwards requests to Ghost on 127.0.0.1:2368.

This design has several benefits:

  • You can host multiple sites (Ghost, API, other apps) on the same server, each on different domains or paths.
  • Nginx can handle static assets, gzip/Brotli, and HTTP/2/3 more efficiently than a raw Node.js process.
  • You get a clean place to manage redirects, HSTS, security headers, and other HTTP niceties.

If you want a broader overview of reverse proxy concepts and examples, our guide on Nginx reverse proxy and simple load balancer setup for small projects walks through several scenarios very similar to Ghost.

Basic Nginx server block for Ghost (HTTP only, first step)

Let us start with a simple HTTP server block (we will add SSL in the next section). Create a new file like /etc/nginx/sites-available/blog.example.com.conf:

server {
    listen 80;
    listen [::]:80;
    server_name blog.example.com;

    # Optional: redirect www blog to non-www
    # if ($host = 'www.blog.example.com') {
    #     return 301 $scheme://blog.example.com$request_uri;
    # }

    # Proxy all requests to Ghost
    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;

        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-Host $host;

        # Increase timeouts slightly for admin operations
        proxy_read_timeout 90;
        proxy_connect_timeout 90;
        proxy_send_timeout 90;
    }
}

Enable the site and test the Nginx configuration:

sudo ln -s /etc/nginx/sites-available/blog.example.com.conf 
  /etc/nginx/sites-enabled/

sudo nginx -t
sudo systemctl reload nginx

At this point, http://blog.example.com should show your Ghost blog, but without HTTPS yet. Leave it this way for a few minutes; we will layer SSL on top in the next step.

Enabling HTTPS and HTTP/2 with SSL

Why SSL matters even for a simple blog

Even if you are just running a personal blog, HTTPS is no longer optional. Browsers warn users on insecure login forms, search engines prefer HTTPS, and features like HTTP/2/3 require TLS. When we design hosting for clients, we default everything to HTTPS, including staging sites.

You can use a free Let’s Encrypt certificate or a commercial certificate, depending on your needs. If you want a deeper comparison, we explain the trade‑offs in our article about Let’s Encrypt vs commercial SSL certificates. For most Ghost blogs, Let’s Encrypt is more than sufficient.

Obtaining a Let’s Encrypt certificate with Certbot

The easiest way on Ubuntu/Debian with Nginx is to use Certbot. Install it from the OS repository or the official snap:

sudo apt install -y certbot python3-certbot-nginx

Then run:

sudo certbot --nginx -d blog.example.com

Certbot will:

  • Verify that blog.example.com points to this server.
  • Request a certificate from Let’s Encrypt.
  • Update your Nginx configuration with SSL directives.
  • Optionally set up an automatic HTTP to HTTPS redirect.

After this step, your Nginx server block will have additional lines like:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name blog.example.com;

    ssl_certificate /etc/letsencrypt/live/blog.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/blog.example.com/privkey.pem;

    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    # ... your proxy_pass config ...
}

server {
    listen 80;
    listen [::]:80;
    server_name blog.example.com;
    return 301 https://$host$request_uri;
}

Certbot also installs a cron job or systemd timer for automatic renewal. You can test it with:

sudo certbot renew --dry-run

If you want to go deeper into HTTPS best practices (HSTS, redirect strategies, mixed content), our guide on full HTTPS migration with 301 redirects, HSTS and SEO‑safe setup walks through a full migration story that applies equally well to Ghost.

Modern TLS and performance tuning

Once HTTPS is in place, we usually add a few extra touches:

  • Enable HTTP/2 (already done by http2 in the Nginx listen directive).
  • Turn on gzip or Brotli compression for HTML, CSS, JS and JSON API responses.
  • Optionally add HSTS once you are confident everything works over HTTPS only.

Most of these can be handled in a small Nginx include file shared by your sites. For an even more performance‑focused setup, especially when combining TLS 1.3, OCSP stapling and Brotli, see our practical guide on configuring TLS 1.3, OCSP stapling and Brotli on Nginx.

Using Ghost as a Headless Blogging Platform

Ghost’s Content API in practice

Ghost exposes a Content API and an Admin API over HTTP. This makes it a very capable headless CMS for blogs, magazines and documentation sites. Typical headless use cases include:

  • Serving a blog frontend built with Next.js, Nuxt, Gatsby, Astro or another static/SSR framework.
  • Sharing content between a website and a mobile app.
  • Publishing posts to multiple brands or domains from a single Ghost instance.

From a hosting perspective, nothing changes on the Ghost side: it still runs as a Node.js service on an internal port and is proxied by Nginx. The difference is how your frontend connects to it and how you structure routing on the reverse proxy.

Routing patterns for headless Ghost

There are two common patterns we implement for clients.

1. Separate subdomains for frontend and Ghost

  • Ghost admin and API at ghost.example.com.
  • Public frontend at blog.example.com or www.example.com.

In this model, Ghost is never directly visible to visitors. Only your editors log in to ghost.example.com, and your frontend consumes the Content API with an API key. Each subdomain has its own Nginx server block, SSL certificate and caching rules.

2. Single domain, path‑based routing

  • Public frontend handles everything under /.
  • Ghost admin and API live under /ghost/ or /content/.

Here, Nginx decides by path which upstream to send traffic to: your frontend (for example a Node.js SSR app) or the Ghost service. A simplified example:

server {
    listen 443 ssl http2;
    server_name example.com;

    # SSL directives omitted for brevity

    # Frontend (Next.js) upstream
    location / {
        proxy_pass http://127.0.0.1:3000;
        # ... proxy headers ...
    }

    # Ghost admin + API
    location /ghost/ {
        proxy_pass http://127.0.0.1:2368;
        # ... proxy headers ...
    }
}

This lets your editors access Ghost at https://example.com/ghost/ while visitors see only the frontend. If you want a broader overview of headless and Jamstack patterns (including static builds and object storage origins), our guide on hosting headless CMS and Jamstack sites is a good companion read.

Where to run the frontend

Depending on your scale and team preferences, you can:

  • Run the frontend on the same VPS or dedicated server as Ghost (simple, cost‑effective for small to medium projects).
  • Use a separate VPS for the frontend if it needs more CPU or has different scaling requirements.
  • Build a fully static frontend and serve it from object storage + CDN, using Ghost only as a content source for builds.

Whatever you choose, the key is to keep your architecture simple at first and only split components when you truly need extra isolation or scaling headroom.

Production Hardening: Process Management, Logs, Backups and Monitoring

Process management and zero‑downtime deploys

If you used Ghost‑CLI, it already set up a systemd service. For custom Node.js headless frontends that live next to Ghost, we recommend using systemd units or a process manager like PM2 and an Nginx reverse proxy in front. We describe a practical, no‑drama pattern in our article on hosting Node.js in production with PM2/systemd, Nginx, SSL and zero‑downtime deploys.

For Ghost itself, upgrades are usually handled by Ghost‑CLI with commands like ghost update, which gracefully restarts the process via systemd.

Logging and diagnostics

When running Ghost and potentially a separate frontend, keep an eye on three log sources:

  • Ghost logs (under /var/www/ghost/content/logs/ by default).
  • Nginx access and error logs under /var/log/nginx/.
  • Systemd journal for Ghost services (journalctl -u ghost_...).

For growing projects, centralised log aggregation (for example with Loki + Promtail or ELK) helps a lot, especially when you add more servers later.

Backups and disaster recovery

Ghost stores content primarily in a MySQL/MariaDB database and in its content/ directory (images, themes, configuration). A minimal backup plan should include:

  • Regular database dumps (mysqldump or XtraBackup) to off‑site storage.
  • File backups of the /var/www/ghost/content/ directory.
  • Configuration backups for Nginx and systemd units.

If you are designing a broader backup policy for multiple sites and apps, our 3‑2‑1 backup article on why the 3‑2‑1 backup strategy works and how to automate backups on cPanel, Plesk and VPS is a good framework to start with.

Security basics

Beyond SSL and updated software, a secure Ghost and headless setup should include:

  • Firewall rules that only allow ports 22, 80 and 443 from the internet. Ghost should only listen on 127.0.0.1.
  • Regular OS and package updates, including Node.js security patches.
  • Strong passwords and 2FA for Ghost admin users where possible.
  • Optionally a Web Application Firewall (WAF), either at the CDN level or on the server (for example ModSecurity in front of Nginx).

For a deeper security checklist around VPS hardening (SSH, firewalls, Fail2ban and more), our guide on how to secure a VPS server without drama provides a step‑by‑step path you can follow for the same server that hosts Ghost.

Putting It All Together and Next Steps

Hosting a Ghost blog or a headless Ghost‑powered publishing stack is not complicated once you see the full picture. You need a clean Node.js runtime, a service manager (systemd or PM2), Nginx as a reverse proxy, and an SSL setup that is automated and well‑understood. On top of this foundation, you can decide whether Ghost will render the blog itself, or whether it will only provide content via its APIs to a separate frontend built with modern frameworks.

At dchost.com we typically start clients on a VPS with enough CPU, RAM and NVMe storage to handle both Ghost and, if needed, a Node.js frontend on the same machine. When the project grows, it is easy to split roles: a dedicated Ghost instance as a pure headless CMS, multiple frontends on separate servers, object storage for media and a CDN in front of everything. The reverse proxy and SSL layer you built for the first small blog still scales nicely in those more advanced architectures.

If you are planning your own Ghost deployment and want help sizing a VPS or dedicated server, or designing a headless architecture that fits your content and traffic patterns, our team at dchost.com can walk through your requirements and propose a concrete, no‑surprises plan. Whether you just need a fast, minimal Ghost blog or a multi‑frontend headless publishing platform, the steps in this guide will give you a solid, production‑ready foundation.

Frequently Asked Questions

Ghost runs on Node.js and expects a background process, reverse proxy and custom ports. Classic shared hosting is usually optimized for PHP and does not allow long‑running Node.js services or custom daemons. For this reason, we strongly recommend hosting Ghost on a VPS or dedicated server where you can install Node.js, run Ghost as a systemd service, and configure Nginx and SSL freely. At dchost.com we typically place small to medium Ghost blogs on a VPS with 1–2 vCPU and 1–2 GB RAM, which is enough for a smooth experience and leaves room for a small headless frontend if needed.

Putting Nginx in front of Ghost as a reverse proxy solves several problems at once. First, Nginx can terminate HTTPS and handle HTTP/2 efficiently, while Ghost can focus on generating content. Second, you can host multiple apps on the same server and route by domain or path, which is especially useful in headless setups where a separate frontend lives next to Ghost. Third, Nginx lets you manage redirects, security headers, gzip/Brotli compression and basic caching for static assets. Exposing Ghost directly on port 2368 would make SSL, multi‑site hosting and advanced HTTP tuning much harder to manage and scale.

Ghost exposes a Content API and Admin API over HTTP. In a headless setup, Ghost runs on a VPS or dedicated server, usually behind Nginx and SSL, and your frontend framework (Next.js, Nuxt, Gatsby, Astro, etc.) fetches content from the Ghost Content API using an API key. You can either put Ghost on a subdomain like ghost.example.com and the frontend on blog.example.com, or route by path (for example /ghost/ for Ghost, / for the frontend) using Nginx. The frontend can be rendered server‑side on the same VPS, on a separate VPS, or even built to static files and served from object storage and a CDN, while Ghost remains your single source of truth for posts and pages.

On a Linux VPS with Nginx, the most convenient way is to use Let’s Encrypt with Certbot. After pointing your domain to the server, you install Certbot and run a command like certbot --nginx -d blog.example.com. Certbot will request a certificate, configure Nginx for HTTPS and set up automatic renewal via a cron job or systemd timer. You can verify renewals with certbot renew --dry-run. If you prefer a commercial SSL certificate, you configure the certificate and key paths manually in the Nginx server block. In both cases, Ghost itself does not need to know about the certificate; Nginx handles TLS, and proxies plain HTTP requests to Ghost on localhost.