> ## Content Index
> Fetch the complete content index at: https://devopscube.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# How to Self-Host Ghost Platform (A Complete Guide)
- URL: https://devopscube.com/self-host-ghost-platform/
- Published: 2026-09-07T06:21:11.000Z
- Updated: 2026-09-07T06:21:11.000Z
- Author: Sulaiman
- Tags: #syntax-highlight, GHOST, DOCKER, #blog, HOW TO GUIDES

Looking for an end-to-end guide to self-host the Ghost platform? Then this guide is for you.

We have been using Ghost for many years now. In fact, DevOpscube is running on the Ghost platform. Although we use managed Ghost hosting, we do development and testing in our self-hosted setup with a proper CI/CD setup.

In this guide, we have covered the following.

- Ghost self-hosted setup architecture
- How all self-hosted Ghost components work
- Setting up the whole stack using Docker Compose
- SMTP setup and configuration for transactional emails
- Mailgun configuration for newsletter setup

and more..

Overall, we have covered all the essential setup you need to **implement a production-level, self-hosted Ghost setup.**

Before we get started with the setup, let's first understand the high-level architecture of a self-hosted Ghost platform.

## Self-hosted Ghost Setup Architecture

The following diagram illustrates the high-level architecture of a self-hosted Ghost setup.

![Self-hosted Ghost setup Architecture](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/08/image-81.png)

Now, let's take a look at the key components of the self-hosted Ghost Platform.

### Caddy

[Caddy](https://caddyserver.com/docs/?ref=devopscube.com) is an open-source web server written in [Go](https://devopscube.com/golang-for-devops/). Unlike other servers such as NGINX, it automatically manages HTTPS routing and TLS certificates.

In our architecture, Caddy is the only [Docker container](https://devopscube.com/what-is-a-container-and-how-does-it-work/) that is exposed to the Internet on ports 80 and 443\. Everything else (MySQL, ActivityPub, and Ghost) is not directly exposed to the Internet.

In this setup, Caddy does two things.

- **Automates HTTPS routing and manages TLS certificates:** If you have a valid domain, Caddy automatically obtains [TLS certificates](https://devopscube.com/configure-ingress-tls-kubernetes/) from a public Certificate Authority such as [Let’sEncrypt](https://letsencrypt.org/?ref=devopscube.com) using the ACME protocol, renews them before they expire, and handles HTTPS connections.
- **Path-based routing and reverse proxying:** Caddy checks the incoming request path and forwards it to the appropriate backend service.

### Ghost CMS 

[Ghost CMS](https://github.com/tryghost/ghost?ref=devopscube.com) is a Node.js based application. It is the core component that serves your public website and provides features such as the Ghost Admin dashboard for writing, editing, and publishing content.

Same like Caddy, Ghost is also deployed as a container using an Alpine image. Caddy receives the public traffic and forwards the requests to Ghost on this port.

Another important Ghost config is the content directory. All the content files of the Ghost platform live in the **`data/ghost`** host directory. For example, uploaded images, themes, and its local configs.

Docker mounts **`data/ghost`** host directory to **`/var/lib/ghost/content`** directory on the Ghost container. So, even if the container is recreated, you won't lose any data.

### MySQL

Here, [MySQL](https://devopscube.com/install-mysql-operator-on-kubernetes/) is the shared database server that is used in this stack. The **`data/mysql`** host directory is where all the data, such as Ghost's posts, pages, [tags](https://devopscube.com/hide-posts-by-tag-in-ghost/), member records, and settings, is stored. It is mounted to **`/var/lib/mysql`** directory on the container. 

Both containers (Ghost and ActivityPub) connect to the same MySQL instance, each using the credentials you set in **`.env`** file (*you'll learn more about it in the configuration section*).

💡

In our implementation, we will be using [MySQL 8.](https://dev.mysql.com/downloads/mysql/8.0.html?ref=devopscube.com) Because Ghost [supports only MySQL 8](https://docs.ghost.org/faq/supported-databases?ref=devopscube.com) in production.

### ActivityPub

[ActivityPub](https://www.w3.org/TR/activitypub/?ref=devopscube.com) gives Ghost an additional way to distribute your content on platforms like [WordPress](https://devopscube.com/migrate-wordpress-to-ghost/), [Mastodon](https://joinmastodon.org/?ref=devopscube.com), and others. When you publish a new post, it can also appear in the feeds of people following your publication on those platforms. 

ActivityPub runs as a dedicated container that listens on port 8080 internally and stores its state in the **`activitypub`** database.

💡

ActivityPub is optional. If you don't want ActivityPub, you can disable it during the setup.

### SMTP Server (For Transactional Emails)

Transactional Emails are one-to-one emails. 

For example, when a visitor subscribes, a new member requests a sign-in link, or you invite a new staff user, Ghost needs to send a verification code to their email address.

The SMTP server is the core component in the setup for sending transactional emails. We will use [Amazon SES ](https://devopscube.com/setup-aws-ses/)as the SMTP provider for transactional emails because Ghost sends them using its standard mail configuration. 

💡

All SMTP server details are configured in the **`.env`** config file.

### Mailgun (For Sending Newsletters)

Newsletters work differently from transactional emails. Publishing one newsletter can generate thousands of individual emails. 

So newsletters are sent via bulk email, using [Mailgun](https://www.mailgun.com/?ref=devopscube.com). This is the **only native option supported by the Ghost platform** for sending bulk email. Once Mailgun is verified and connected to Ghost using Mailgun's API key, transactional emails and newsletters can use their separate delivery systems.

## Why Docker Compose?

We strongly recommend you use Docker Compose for the setup. Here is why.

If you install all the Ghost components directly on the Ubuntu server, you need to manage all the dependencies on the server itself. The problem is that system updates and dependency changes can affect the Ghost installation.

But when you use Docker Compose, the problem gets solved.

You can start the entire stack using a single **`docker compose up -d`** command.

Also, if a new [Docker image](https://devopscube.com/build-docker-image/) is available, updating the entire stack with Docker Compose is easier. At the same time, if an image update causes problems, it is much easier to roll back to the previous image version.

And it is easier to recreate the whole stack on a different server if required.

Now let's get started with the setup.

## Setup Prerequisites

These are some prerequisites you need before starting this implementation. 

- A valid [Domain](https://devopscube.com/map-cloudways-server-to-a-domain/) name.
- SMTP server details.

💡

We have used [Amazon SES](https://devopscube.com/setup-aws-ses/) as the SMTP Service provider. You can use any SMTP provider.

Now, let's start our implementation.

## Setting Up Ghost with Docker 

Follow the steps below to set up a complete, working Ghost environment using Docker.

### Step1: Provision an Ubuntu Server

We need Ubuntu Server 24.04 (LTS) with 2 vCPUs, 4 GB of RAM, and 40 GB of disk space.  
  
You can use cheaper hosting platforms like [DigitalOcean](https://devopscube.com/get-free-digital-ocean-credits/) or [Cloudways](https://devopscube.com/cloudways-promo-codes/) to host your Ubuntu Server.

📌

Make sure you configure your Ubuntu Server with a static public IP address. It should be mapped as an A record in your website's DNS settings.

### Step2: Install Docker on Ubuntu Server

We need to install Docker for this implementation. Use the following one-line command to install it on your Ubuntu server.

```Bash
curl -sSL https://get.docker.com/ | sudo sh
```

### Step 3: Create a New User

Then we need to create a new user by executing the following commands. Make sure you run the commands as the **`root`** user.

```Bash
adduser  deploy
usermod -aG sudo deploy
```

Here, we have created the user named **`deploy`** and modified the user's attributes with configuration settings.

Now we need to switch to the **`deploy`** user using the following command.

```Bash
su deploy
```

### Step 4: Create a Directory and Change Ownership

First, we need to create a directory inside the path **`/opt/ghost`** and change the ownership of the directory, which sets both the user and group to **`deploy`** using the following command.

```Bash
sudo mkdir -p /opt/ghost && sudo chown deploy:deploy /opt/ghost
```

### Step 5: Clone the repository and rename the env and Caddyfile

Now we need to clone the repo into the new directory we have created.

```Bash
git clone https://github.com/TryGhost/ghost-docker.git /opt/ghost
```

Now get into the directory using the **`cd`** command.

```Bash
cd /opt/ghost
```

There will be an **`.env.example`** file. Rename it to the **`.env`** file using the following command.

```Bash
cp .env.example .env
```

Do the same for the **`Caddyfile.example`** file, which is present inside the **`/caddy`** directory.

```Bash
cp caddy/Caddyfile.example caddy/Caddyfile
```

💡

The [Caddyfile](https://caddyserver.com/docs/caddyfile?ref=devopscube.com) is a configuration file that defines how the [Caddy web server](https://caddyserver.com/?ref=devopscube.com) handles websites, reverse proxies, and security.

### Step 6: Generate Database Credentials

Run the following commands to generate two random passwords for the database.

```Bash
echo "ROOT: $(openssl rand -hex 32)"
echo "USER: $(openssl rand -hex 32)"
```

This command generates two random 32-byte security keys, each as a 64-character hex string, for MySQL authentication.

Copy both keys. We need to use them in the **`.env`** file for the **`DATABASE_ROOT_PASSWORD`** and **`DATABASE_PASSWORD`**.

### Step 7: Change .env File Permissions

Using the following command, change permissions for the `.env` file.

```Bash
chmod 600 .env
```

This command gives read and write permission for the **`.env`** file. It also ensures that only the file owner can read or modify it.

### Step 8: Configure Domain, SMTP and MySQL Credentials in the .env file

The **`.env`** file contains the key configurations of the following.

- Domain name
- Activitypub endpoint
- Database credentials
- SMTP Configurations (For Transactional emails)

Open the **`.env`** file using the [VIM editor](https://devopscube.com/linux-vi-editor-shortcuts-beginners/) and change the values accordingly.

💡

If you want to disable ActivityPub, you can comment the **`COMPOSE_PROFILES`** variable in the below **`.env`** file.

```Bash
COMPOSE_PROFILES=activitypub

DOMAIN=<Your-domain-name>
ACTIVITYPUB_TARGET=activitypub:8080

DATABASE_ROOT_PASSWORD=<paste the first key you generated>
DATABASE_PASSWORD=<paste the second key you generated>

mail__transport=SMTP
mail__options__host=email-smtp.<YOUR_REGION>.amazonaws.com
mail__options__port=2587
mail__options__secure=false
mail__options__auth__user=<ses-smtp-user>
mail__options__auth__pass=<ses-smtp-pass>
mail__from='DevOps Project <noreply@example.com>'

UPLOAD_LOCATION=./data/ghost
MYSQL_DATA_LOCATION=./data/mysql

```

Enter your specific domain name.

Make sure you replace the **`DATABASE_ROOT_PASSWORD`** and **`DATABASE_PASSWORD`** with your actual values you got from Step 6.

Also, you need to add your specified AWS region in the **`mail__options__host`** variable.

💡

If you are using [DigitalOcean](https://docs.digitalocean.com/support/why-is-smtp-blocked/?ref=devopscube.com) Droplets, SMTP ports 25, 465, and 587 are blocked by default. In such cases, you can use alternate ports provided by the SMTP server.  
  
For example, AWS SES provides 2465 and 2587 as alternatives to 465 and 25.

### Step 9: Start your Ghost Platform Using Docker

Below are the two commands to start your Ghost stack using Docker [containers](https://devopscube.com/what-is-a-container-and-how-does-it-work/).

```Bash
sudo docker compose pull
```

This pull command downloads the [Docker](https://devopscube.com/run-docker-in-docker/) images specified in **`docker-compose.yml`** file.

You can verify using the output that the images are pulled.

```Bash
[+] pull 48/48
 ✔ Image caddy:2.10.2-alpine@sha256:953131cfea8e12bfe1c631a36308e9660e4389f0c3dfb3be957044d3ac92d446  Pulled 19.8s
 ✔ Image ghcr.io/tryghost/activitypub:1.2.9@sha256:f950017169c778f90bc1d4097c0c83735dd88ee26a7dfac... Pulled 38.0s
 ✔ Image ghcr.io/tryghost/activitypub-migrations:1.2.9@sha256:f8a376e83187cc927fd6286a9e825b71056b... Pulled  7.5s
 ✔ Image mysql:8.0.44@sha256:f37951fc3753a6a22d6c7bf6978c5e5fefcf6f31814d98c582524f98eae52b21         Pulled 38.0s
 ✔ Image ghost:6-alpine                                                                               Pulled 71.7s
```

Now let's start the containers.

```Bash
sudo docker compose up -d
```

The above command starts all the containers defined in your Docker Compose configuration.

You can verify with the following output,

```
[+] up 8/8
 ✔ Network ghost_ghost_network           Created                                                              0.1s
 ✔ Volume ghost_caddy_config             Created                                                              0.0s
 ✔ Volume ghost_caddy_data               Created                                                              0.0s
 ✔ Container ghost-db-1                  Healthy                                                             29.3s
 ✔ Container ghost-activitypub-migrate-1 Exited                                                              28.4s
 ✔ Container ghost-activitypub-1         Started                                                             28.6s
 ✔ Container ghost-ghost-1               Started                                                             29.4s
 ✔ Container ghost-caddy-1               Started                                                             29.9s
```

You can use the following command to verify,

```Bash
sudo docker compose ps
```

It shows the following output.

```Bash
NAME                  IMAGE                                                                                                        COMMAND                  SERVICE       CREATED             STATUS                       PORTS
ghost-activitypub-1   ghcr.io/tryghost/activitypub:1.2.9@sha256:f677e8af8df41418c3bfd63b4ec76b9bf2bbd85bde7da0aa9ecd86e41a230835   "docker-entrypoint.s…"   activitypub   About an hour ago   Up About an hour             8080/tcp
ghost-caddy-1         caddy:2.10.2-alpine@sha256:953131cfea8e12bfe1c631a36308e9660e4389f0c3dfb3be957044d3ac92d446                  "caddy run --config …"   caddy         About an hour ago   Up About an hour             0.0.0.0:80->80/tcp, [::]:80->80/tcp, 0.0.0.0:443->443/tcp, [::]:443->443/tcp, 443/udp, 2019/tcp
ghost-db-1            mysql:8.0.44@sha256:f37951fc3753a6a22d6c7bf6978c5e5fefcf6f31814d98c582524f98eae52b21                         "docker-entrypoint.s…"   db            About an hour ago   Up About an hour (healthy)   3306/tcp, 33060/tcp
ghost-ghost-1         ghost:6-alpine                                                                                               "docker-entrypoint.s…"   ghost         33 minutes ago      Up 33 minutes                2368/tcp
```

You can watch the live logs of the containers running in your Ghost setup using the following command.

```Bash
sudo docker compose logs -f ghost caddy
```

You can verify the logs as follows:

```Bash
ghost-1  | [2026-08-28 13:24:39] INFO Ghost is running in production...
ghost-1  | [2026-08-28 13:24:39] INFO Your site is now available on https://ghost.devopsproject.dev/
ghost-1  | [2026-08-28 13:24:39] INFO Ctrl+C to shut down
ghost-1  | [2026-08-28 13:24:39] INFO Ghost server started in 3.347s
ghost-1  | [2026-08-28 13:24:39] WARN Database state requires initialisation.
ghost-1  | [2026-08-28 13:24:39] INFO Creating table: newsletters
ghost-1  | [2026-08-28 13:24:39] INFO Creating table: posts
ghost-1  | [2026-08-28 13:24:40] INFO Creating table: posts_meta
ghost-1  | [2026-08-28 13:24:40] INFO Creating table: gift_links
ghost-1  | [2026-08-28 13:24:40] INFO Creating table: post_gift_links
ghost-1  | [2026-08-28 13:24:40] INFO Creating table: users
ghost-1  | [2026-08-28 13:24:40] INFO Creating table: posts_authors
ghost-1  | [2026-08-28 13:24:40] INFO Creating table: roles
ghost-1  | [2026-08-28 13:24:40] INFO Creating table: roles_users
ghost-1  | [2026-08-28 13:24:40] INFO Creating table: permissions
ghost-1  | [2026-08-28 13:24:40] INFO Creating table: permissions_users
ghost-1  | [2026-08-28 13:24:40] INFO Creating table: permissions_roles
```

You can check the output for any errors. If there are no errors, press `CTRL + C` to stop the output.

### Step 10: Ghost Admin Setup

Now we need to create an admin user for our Ghost platform.

Open your browser, enter your domain name, and add **`/ghost`** at the end. And hit Enter.

For example, **`https://<your-domain>.com/ghost`**

You will see a page like the one below. This is the Ghost Admin page, where you need to enter the following.

- Site title
- Your full name
- Email Address
- Password

![Ghost admin page showing the account setup page with details to fill up and Create account & start publishing button.](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/08/image-66.png)

Enter the valid details. Then click on the Create Account button. You will be redirected to your Ghost admin page.

That's it, you have now successfully set up a self-hosted Ghost platform using Docker Compose.

## Configuring Mailgun for Newsletter (Optional)

If you want to send newsletters to your subscribers, you need to set up Mailgun.

Let's have a look at how we can set it up. 

### Signup for Mailgun

Open [Mailgun](https://www.mailgun.com/?ref=devopscube.com) in your browser. And click on the Get started for free option.

![Sinch Mailgun signup page showing the free account registration form with details need to be filled up and an option to add a credit card.](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/08/image-74.png)

Uncheck the "Add a credit card" option to get the free tier. Then fill in the required details.

You will get an email from the Mailgun support team to verify your account. Click on it and complete your verification.

### Set up a domain on Mailgun

After completing the verification, we need to add our domain by selecting the Domains option under the Send section. 

In the top-right corner, you will see the Add new domain option, as shown below.

![Mailgun Domains page showing the Send section and Domains tab, with the Add new domain button.](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/08/image-78.png)

Add your domain name and specify the region in which your server is located. For testing purposes, a 1024-character DKIM key is enough. **For production, choose 2048 characters**, as 1024 is deprecated and may be blocked by providers like Gmail.

![Mailgun Add new domain page showing a domain name field, region and advanced settings, and the Add Domain button.](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/08/image-79.png)

💡

Here, [DKIM](https://www.cloudflare.com/learning/dns/dns-records/dns-dkim-record/?ref=devopscube.com) refers to a security method of encrypting and adding a digital signature to outgoing emails.

### Configure MX Records in your DNS Provider

The next step is to configure the mail-related DNS records (MX, CNAME, SPF, DKIM from Mailgun with your DNS provider.

Go to Domain Settings. Under the DNS Records page, you will find four records named [TXT (SPF)](https://www.cloudflare.com/learning/dns/dns-records/dns-txt-record/?ref=devopscube.com) and [TXT (DKIM](https://www.cloudflare.com/learning/dns/dns-records/dns-dkim-record/?ref=devopscube.com)), MX, and CNAME, as shown in the image below.

![Mailgun domain settings dashboard showing DNS Records tab with TXT SPF and DKIM sending records used to verify email sending domain.](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/08/image-86.png)

Scroll down; you will find two [MX](https://www.cloudflare.com/learning/dns/dns-records/dns-mx-record/?ref=devopscube.com) records and also a [CNAME](https://www.cloudflare.com/learning/dns/dns-records/dns-cname-record/?ref=devopscube.com) record. 

![domain settings dashboard showing DNS Records tab with the other 2 records such as MX records and CNAME for our DNS Provider](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/08/image-84.png)

Now, here is the important step.

Go to your **DNS provider** where you manage the website's domain name and add all four records, pointing to the correct values as listed on the Mailgun DNS page.

****Note:** The DNS configuration varies by DNS provider. However, the records remain the same.

### Configure the Mailgun API Key in Ghost

Now that we have configured our domain with Mailgun, we need to do one final step: connect Mailgun to the Ghost platform using the Mailgun API key.

Go to Mailgun, Domain settings under the section of Sending keys, and click on Add sending keys. Add a short description. Then click on Create sending key as shown below.

![Mailgun Domain Settings page shows the Sending Keys tab, with the Create sending key button.](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/08/image-94.png)

It will generate an API key. Copy it. 

Now, open your self-hosted Ghost platform. On the settings page, search for Newsletter to find the Mailgun section. Add your Mailgun region (there are only two regions: US and EU). Add your Mailgun domain, then paste the private API key generated in the previous step as shown below.

![Ghost Admin page shows adding the Mailgun API key in the Newsletter section with our domain name.](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/08/image-93.png)

Now all the Mailgun configurations for newsletter setup are done.

## Test and Validate the Ghost Setup

Now we need to conduct a comprehensive health check to validate the entire setup. These checks confirm TLS certificate validity and HTTP-to-HTTPS redirection.

Let's execute the testing commands one by one.

⚠️

****Important Note:** In the following validation commands, we have used our domain name **`ghost.devopsproject.dev`** **.** When you execute each command, replace it with your configured domain name.

### Check TLS Certificate Validity

First, we need to verify if the Let's Encrypt TLS Certificate is configured properly.

Use the following command to validate the certificate. 

```Bash
curl -sI https://ghost.devopsproject.dev/ | head -1

echo | openssl s_client -connect ghost.devopsproject.dev:443 \
  -servername ghost.devopsproject.dev 2>/dev/null \
  | openssl x509 -noout -dates
```

The output should look like the following.

```Bash
HTTP/2 200
notBefore=Aug 24 05:31:04 2026 GMT
notAfter=Nov 22 05:31:03 2026 GMT
```

### Validate HTTP to HTTPS redirection

Now we need to confirm that the HTTP traffic is automatically redirected to HTTPS, so that no request gets a response over an unencrypted connection.

Use the following curl command to verify the redirection from HTTP to HTTPS

```Bash
curl -sI http://ghost.devopsproject.dev | grep -i location
```

You should see the redirected output as given below.

```Bash
Location: https://ghost.devopsproject.dev/
```

## Test and Validate Email Deliverability

When you set up Ghost for production testing, transactional and email newsletter deliverability is very important.

Let's test both.

### Test the Transactional Email by Sending a Staff Invite

First of all, we need to run an email test to verify whether we can invite a staff member using their Email ID. For this, we are using [Amazon Simple Email Service ](https://aws.amazon.com/ses/?ref=devopscube.com)(SES).

To test this. Open your Ghost admin page.

After that, you will get to an analytics page. In the bottom-left corner, you will see a settings button. Click on that.

![Ghost CMS admin dashboard shows the Settings menu option in the sidebar, with Analytics view showing member growth and latest post performance.](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/08/image-67.png)

Now click on the Staff button as shown below.

![Ghost Admin General Settings page shows the Staff option, along with site title, description, and timezone settings.](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/08/image-68.png)

Then click on the Invite People button. 

![Ghost admin settings showing Staff section with 'Invite people' option highlighted, plus Meta data settings for search engine and social account optimization.](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/08/image-69.png)

Enter an email address (must be different from your admin email). Also, select the staff member's role according to your preferences. Then click the Send Invitation button below.

![Ghost Admin Invite a new staff user shows an email address, contributor role selected with the Send invitation button.](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/08/image-59.png)

You can verify that the Invitation Sent notification is shown on the bottom-left side.

![Ghost Admin Staff settings showing an invited contributor and a confirmation message that the staff invitation was successfully sent.](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/08/image-60.png)

And now, check your email. You will receive an invitation to activate your account, as shown below.

![Gmail screenshot showing a Ghost account invitation email from DevOps Project, with a button to activate the account.](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/09/image-61.png)

Click the **Click here to activate account** button. 

That's it. You have now successfully verified transactional email deliverability.

### Testing the Newsletter Email Deliverability 

We have already configured the newsletter bulk email sending setup for our self-hosted Ghost platform using Mailgun.

Now, we just need to test the setup by creating a new post, publishing it, and sending it to your email subscribers.

For example, I created a sample post, added the email addresses of five members, and then hit publish using the "Publish and email" option.

![Ghost publishing screenshot shows the Publish and email option, with all 5 subscribers and the Continue, final review button.](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/08/image-95.png)

The newsletter email is successfully delivered, as shown below.

![Gmail screenshot showing a newsletter email titled Testing Newsletter Email sent from a self-hosted Ghost platform.](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/08/image-96.png)

You can also verify whether the emails are delivered and opened by the members in the Mailgun application UI. Under the Reporting section, there will be an option called Logs, as shown in the image below.

![Mailgun Reporting Logs dashboard showing newsletter email delivery and open events from Self-hosted Ghost.](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/08/image-97.png)

We have now successfully tested the newsletter email deliverability.

You now have a production ready self-hosted ghost setup.

## Ghost Self-Hosting FAQs

Now let's clarify some of the frequently asked Questions about the topic.

### 1\. How safe is Ghost self-hosting?

It's only safe if you maintain it properly with best security practices, such as two-factor authentication, Backups, and monitoring.

### 2\. What are the different deployment options for self-hosting Ghost?

There are many other ways we can self-host the Ghost platform. We can self-host on a plain vanilla server or use [Docker](https://devopscube.com/what-is-docker/) or [Kubernetes](https://devopscube.com/kubernetes-architecture-explained/). Also, many hosting providers offer one-click deployment options for a fully configured Ghost setup. 

## Conclusion

We have successfully set up a self-hosted Ghost platform from scratch and validated it by sending newsletters.

Self-hosting gives you [SSH](https://devopscube.com/generate-ssh-key-pair/) and database access, full control over the configuration, and the freedom to modify it.

However, you need to handle operational tasks, such as server updates, backups, security patching, and email delivery. 

If you cannot handle the administrative overhead, Ghost(Pro) is the better choice. Hosting infrastructure, backups, updates, and support are handled for you, and you only need to write and publish.

We follow the hybrid approach. We use a self-hosted setup for development, with robust CI/CD practices, and managed hosting for our production website. 

Over to you!

Are you planning to self-host Ghost, or are you already running it on your own server?

If you followed this guide, let us know how the setup went. 

And if you ran into any issues along the way, please leave a comment below. We will try our best to help.