Self Host n8n with Custom Domain + SSL on Ubuntu 24.04

A simple setup using docker compose
October 19, 2025 by
Self Host n8n with Custom Domain + SSL on Ubuntu 24.04
Alixsander Haj Saw
| No comments yet

Introduction

You can find the video version of this article by clicking here.

This article covers self hosting n8n using your own domain. The setup is simplified as it is using docker for the 3 services we will be launching, n8n, nginx (web server) and certbot (generates free Let's Encrypt certificates).

The setup is expandable by adding additional containers, for example you could add a pgvector container to store vectors inside your workflow and later retrieve those vectors for further processing.

Prerequisites

  • An Ubuntu server (I am using 24.04)
  • An A record pointing your domain to the server
  • Hustle

For easier setup, add your domain name in the bellow field and click Apply to tailor this article with your domain:


IMPORTANT: If you have UFW enabled, make sure it allows traffic over http and https, you can do so by running:

sudo ufw allow http
sudo ufw allow https



Docker and Compose Setup

We will be launching our containers using Docker which must be installed. It can be installed using the official Docker documentation found here. The steps are as follows:

Set up Docker's apt repository:

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

Install the Docker packages:

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Now with Docker installed we can create our compose.yml file which will give instructions on which services we'd like to create. I recommend creating a docker directory inside your users home directory and within it include our setup:

cd
mkdir -p docker/n8n && cd docker/n8n
nano compose.yml

Include the following directives inside the compose.yml file:

services:
  nginx:
    image: nginx:latest
    container_name: nginx
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./certbot/www:/var/www/certbot
      - ./certbot/conf:/etc/letsencrypt
    ports:
      - "80:80"
      - "443:443"
    depends_on:
      - n8n
    restart: always

  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - ./certbot/www:/var/www/certbot
      - ./certbot/conf:/etc/letsencrypt
    entrypoint: >
      sh -c "trap exit TERM; while :; do
        certbot renew --webroot -w /var/www/certbot --quiet;
        sleep 12h & wait $${!};
      done"

  n8n:
    image: n8nio/n8n
    volumes:
      - n8n_data:/home/node/.n8n
    environment:
      - N8N_HOST=example.com
    restart: always


volumes:
  n8n_data:

Let's breakdown the compose.yml file:

n8n container has a volume which stores our users/credentials and workflows. An environment variable that specifies our domain for n8n to work properly, and the restart option is used to keep the container running by restarting it in case it crashes/stops.

The nginx has 3 volumes, ./nginx/conf.d contains will contain our nginx configuration for serving n8n using our domain securly using ssl, the ./certbot/www volume is shared with the certbot container to complete the SSL acme challenge, and the ./certbot/conf volume is shared with certbot which will contain our SSL certificates once they are generated. Depends on section specifies that nginx depends on n8n, which ensures n8n is running before nginx.

Certbot container has 2 volumes shared with nginx as mentioned above. The entrypoint section is responsible for renewing the SSL certificate by checking its expiry twice a day.


Now we can start nginx and n8n which will create the necessary directories for us by running:

sudo docker compose up nginx -d

Since nginx depends on n8n, the n8n container will automatically start before nginx without mentioning it.


Nginx Initial Configuration

After starting nginx, the necessary directories are created, now we need to add the initial nginx configuration which will enable us to generate SSL certificates by completing an acme challenge using our domain name:

cd nginx/conf.d
sudo nano default.conf

Then add the following directives:

server {
    listen 80;
    server_name example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        proxy_pass http://n8n:5678;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
    }
}

Now restart the nginx service by running:

sudo docker compose restart nginx

Now if you visit your page over http (http://example.com/) you should see the following page:

This is completely fine, and we'll fix it after generating and using SSL certificates.


Generating Certificates using Certbot

You'll notice inside our nginx configuration in the previous section that we added a location block for the acme-challenge, now we should be able to generate certificates using the certbot container by running:

sudo docker compose run --rm --entrypoint certbot certbot \
  certonly --webroot -w /var/www/certbot \
  -d example.com

You'll be prompted to accept the terms and services and a few other things after which your certificates will be generated.


Nginx Final Configuration over HTTPS

Now we can add the SSL certificates to our nginx server block and serve traffic over HTTPS. Replace the contents of default.conf with the following:

server {
    listen 80;
    server_name example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}


server {
    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;

    location / {
        proxy_pass http://n8n:5678;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
    }
}

Now restart nginx:

sudo docker compose restart nginx

This will redirect all the unsecure traffic to https.

Now you should be able to access your n8n application by vising the domain example.com, create a user and start building your workflows.

I've created a simple workflow that can monitor your SSL certificate status and send notifications regarding expiry which you can find here.


Sign in to leave a comment