Deploy n8n on Hetzner Cloud: A Step-by-Step Guide for Automation Success (with Redis!) on latest Ubuntu

Unlock the power of workflow automation with n8n on Hetzner Cloud! At Blorax, we’re always looking for ways to help our clients optimize their operations and achieve peak efficiency. That’s why we’re excited to share this comprehensive guide on deploying n8n, a powerful and versatile open-source workflow automation platform, on Hetzner Cloud. This guide includes integrating Redis for improved performance and scalability, ensuring a smooth and reliable automation experience.

Why n8n on Hetzner Cloud?

  • Hetzner Cloud: Offers cost-effective, high-performance cloud servers focusing on reliability and scalability.
  • n8n: Provides a visual, node-based interface for designing and automating complex workflows across various applications and services.
  • Redis: Enhances n8n’s performance by providing efficient queue management, which is especially crucial for handling concurrent workflow executions.

This guide is perfect for:

  • Developers seeking a self-hosted automation solution.
  • Businesses looking to streamline workflows and integrate various systems.
  • IT professionals aiming to deploy a secure and scalable automation platform.

Prerequisites:

Before you begin, ensure you have the following:

  • A Hetzner Cloud account (https://www.hetzner.com/cloud).
  • An SSH client (e.g., PuTTY, Terminal).
  • A domain name (recommended for easy access and HTTPS configuration).
  • A basic understanding of Linux command-line operations.

Step 1: Server Creation on Hetzner Cloud

  1. Log in to your Hetzner Cloud console.
  2. Add a new server.
  3. Select your desired location.
  4. Choose Ubuntu 24+ LTS (recommended).
  5. Select a server type (CX11 or CX21 to start). For production, consider CX31 or larger.
  6. Add an SSH key (strongly recommended for enhanced security).
  7. Set a hostname (e.g., n8n-server).
  8. Create the server.
  9. Define your domain. We will use as an example n8n.blorax.com and add A record for it in your DNS provider.

Step 2: SSH Connection and Initial Setup

Connect to your server via SSH:

ssh root@<your_server_ip_address>  #Password Authentication
ssh -i /path/to/your/private/key root@<your_server_ip_address> #SSH Key Authentication

Update and upgrade packages:

sudo apt update && apt upgrade -y

Step 3: Install Docker and Docker Compose

Docker simplifies the deployment process by containerizing n8n and its dependencies.

Install Docker:

sudo apt install apt-transport-https ca-certificates curl software-properties-common -y
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y
sudo systemctl start docker
sudo systemctl enable docker

Install Docker Compose:

sudo apt install docker-compose -y

Step 4: Deploying n8n with Docker Compose

Create a project directory:

mkdir /opt/n8n-server
cd /opt/n8n-server

Create docker-compose.yml:

nano docker-compose.yml

Add those codes:

#version: "3.9" - Optimized by Blorax.com

services:
  n8n:
    image: n8nio/n8n
    restart: always
    ports:
      - "5678:5678"
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
      - DB_POSTGRESDB_USER=${POSTGRES_USER}
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - N8N_HOST=${N8N_HOST}
      - N8N_PORT=${N8N_PORT}
      - N8N_PROTOCOL=${N8N_PROTOCOL}
      - N8N_EMAIL=${N8N_EMAIL_ADDRESS}
      - N8N_PASSWORD=${N8N_PASSWORD}
      - EXECUTIONS_DATA_SAVE_ON_ERROR=all
      - N8N_URL_BASE_PATH=${N8N_URL_BASE_PATH}
      - TRUST_PROXY_SSL_HEADERS=1
      - WEBHOOK_URL=${WEBHOOK_URL}
      - N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true

    volumes:
      - n8n_data:/home/node/.n8n
    networks:
      - internal
    depends_on:
      - postgres
      - redis
      - qdrant

  postgres:
    image: postgres:15
    restart: always
    environment:
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - internal

  redis:
    image: redis:latest
    restart: always
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - internal

  qdrant:  
    image: qdrant/qdrant
    restart: unless-stopped
    ports:
      - "127.0.0.1:6333:6333"
    volumes:
      - qdrant_storage:/qdrant/storage
    environment:
      QDRANT__SERVICE__API_KEY: ${QDRANT_API_KEY}
    networks:
      - internal

networks:
  internal:
    driver: bridge

volumes:
  n8n_data:
  postgres_data:
  redis_data:
  qdrant_storage:

Create .env:

nano .env

Add those codes:

# Optimized by Blorax.com
# n8n core
N8N_HOST=n8n.blorax.com
N8N_PROTOCOL=https
N8N_PORT=5678
N8N_EDITOR_BASE_URL=https://n8n.blorax.com/
WEBHOOK_URL=https://n8n.blorax.com/
N8N_URL_BASE_PATH=/
TRUST_PROXY_SSL_HEADERS=1

# n8n auth & security
N8N_EMAIL_ADDRESS=[email protected]
N8N_PASSWORD=YourStrongN8NPassword123!
N8N_ENCRYPTION_KEY=VeryLongEncryptionKeyGeneratedWithOpenSSL
N8N_USER_MANAGEMENT_JWT_SECRET=YourRandomLongSecret123#

# database (n8n)
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=n8n-p-database
DB_POSTGRESDB_USER=n8n-p-user
DB_POSTGRESDB_PASSWORD=YourVeryStrongPostgresPassword123!@#

# database (postgres container) #THE SAME AS DB_POSTGRESDB_* Credentials Above 
POSTGRES_DB=n8n-p-database
POSTGRES_USER=n8n-p-user
POSTGRES_PASSWORD=YourVeryStrongPostgresPassword123!@#

# redis
N8N_QUEUE_MODE=redis
REDIS_HOST=redis
REDIS_PORT=6379

# execution behavior
EXECUTIONS_DATA_SAVE_ON_ERROR=all
N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true

# MCP / API
QDRANT_API_KEY=YourRandomQDRANTKey123

Use a STRONG password for every configuration. Use the command in the terminal to generate and copy the output value for N8N_ENCRYPTION_KEY:

openssl rand -base64 32

Start the n8n stack:

docker-compose up -d

Step 5: Obtain SSL Certificates with Let’s Encrypt

Secure your n8n instance with HTTPS using Let’s Encrypt (we will use nginx as a proxy).

Install Certbot:

sudo apt install certbot python3-certbot-nginx

Obtain a certificate:

sudo certbot --nginx -d n8n.blorax.com

Step 6: Configure Nginx as a Reverse Proxy for HTTPS

Nginx provides essential security and reverse proxy functionality.

Install Nginx:

sudo apt install nginx

Create a new Nginx configuration file (/etc/nginx/sites-available/n8n):

sudo nano /etc/nginx/sites-available/n8n-server

Add those codes (don’t forget to change the domain name):

server {
    listen 80;
    server_name n8n.blorax.com;

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

server {
    listen 443 ssl http2;
    server_name n8n.blorax.com;

    ssl_certificate /etc/letsencrypt/live/n8n.blorax.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/n8n.blorax.com/privkey.pem;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    include /etc/nginx/snippets/ssl-params.conf;
    include /etc/letsencrypt/options-ssl-nginx.conf;

    location / {
        proxy_pass http://localhost:5678/;
		proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
		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_set_header X-Nginx-Proxy true;
		
		proxy_http_version 1.1;
		proxy_cache_bypass $http_upgrade;
		
		# SSE-specific fixes
		proxy_buffering off;
		proxy_cache off;
		proxy_read_timeout 3600s;
		proxy_send_timeout 3600s;
		
		# Prevent buffering & compression (MCP SSE)
		gzip off;
    }

    location ~ /\.git {
        deny all;
        return 404;
    }
}

Create/edit /etc/nginx/snippets/ssl-params.conf:

sudo nano /etc/nginx/snippets/ssl-params.conf

Add those codes (it should look like the following):

ssl_session_cache shared:MozSSL:10m;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/n8n.blorax.com/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

If the file “ssl-params.conf” is causing problems, comment out those options

Link this file:

 sudo ln -s /etc/nginx/sites-available/n8n-server /etc/nginx/sites-enabled

Unlink file default (if exists):

sudo rm /etc/nginx/sites-enabled/default

Test nginx:

sudo nginx -t

If you have problems, read the error logs and try to solve the problem with our tips or with the documentation

Restart nginx:

sudo systemctl reload nginx

Step 7: Securing The Server

Enable the firewall and give permission to access

sudo ufw allow OpenSSH
sudo ufw allow 5678/tcp 
sudo ufw allow 80/tcp 
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status

Step 8: Accessing n8n

Open your web browser and navigate to https://n8n.blorax.com (use your domain). You should now see your n8n instance with a secure HTTPS connection.

Best Practices and Considerations:

  • Security: Use strong, unique passwords and keep your server updated.
  • Backups: Implement regular backups of your n8n data and configuration files.
  • Monitoring: Monitor your server’s resources to ensure optimal performance.

Note. If you need to access Qdrant:

ssh -L 6333:127.0.0.1:6333 root@YOUR_SERVER_IP

Then open in your browser: http://localhost:6333

Step 9: Update self-hosted n8n (Docker)

Best and safe commands for self-hosted n8n on Docker to be updated (run the following commands one by one and wait for them to complete):

# 1. Navigate to your n8n directory (set your correct path)
cd /opt/n8n-server 

# 2. Shut down your current n8n services
docker-compose down

# 3. Pull the latest version of the n8n image
docker-compose pull n8n

# 4. Restart the services with the new image
docker-compose up -d

# 5. Monitor the startup process (Optional but Recommended)
docker-compose logs -f n8n

# 6. Clean up old, unused images (Very useful, but optional)
docker image prune

Conclusion

By following these steps, you can successfully deploy a secure and scalable n8n automation platform on Hetzner Cloud with Redis. This guide provides a solid foundation for unlocking the full potential of workflow automation, enabling you to streamline your operations and achieve new levels of efficiency.

Need Expert Help?

At Blorax, we specialize in helping businesses like yours implement cutting-edge automation solutions. Contact us today for a free consultation and let us help you transform your workflows! Visit our website at https://blorax.com to learn more.

Share this post

Comments (3)

  • GLab Reply

    This might need a serious rework.
    You indicate to set a hostname without showing how.
    Same thing for DNS and domain – being the critical step for the nginx setup.

    ssl should be shown before the .env
    The version for the docker compose should be checked or maybe removed depending on the user installation.

    Other than that it’s looking good so far. I’ll have to find a way to do DNS stuff before continuing though…

    July 19 at 06:31
  • GLab Reply

    Thanks a lot for the tutorial it worked almost like a charm.

    Since nginx is handling https proxy, n8n should not be configured to do so.
    In the docker compose yml: set N8N_PROTOCOL=http instead of N8N_PROTOCOL=https
    Also add a webhook url in .env WEBHOOK_URL=https://n8n.blorax.com
    Then in the compose again at the n8n service: add to environment – WEBHOOK_URL=${WEBHOOK_URL}

    Now nginx will reverse proxy to HTTP and handle SSL certification instead of n8n trying to do it bypassing nginx.
    This is a problem because if you try to do some oauth with google you will get SSL_ERROR_RX_RECORD_TOO_LONG because the redirect url will be https://n8n.blorax.com:5678/rest/oauth2-credential/callback
    Here we have the 5678 port served by n8n and buggering the ssl certificate. We want a clean https://n8n.blorax.com/rest/oauth2-credential/callback without any port. This should fix it.

    July 21 at 09:24
    • Roman Muchka Reply

      Hello, and thank you for this incredibly valuable feedback! You have pinpointed one of the most common stumbling blocks when setting up n8n behind a reverse proxy, especially when it comes to using OAuth2 with services like Google. Your analysis is spot on — the port :5678 appearing in the callback URL is the absolute root of the problem.

      The issue you’re seeing, SSL_ERROR_RX_RECORD_TOO_LONG, happens because your browser is trying to speak HTTPS to a port (:5678) that is serving plain, unencrypted HTTP. Nginx is handling the SSL on port 443, but the n8n application itself, listening on 5678, is not configured for SSL.

      The key question is: Why is n8n generating a URL with the port in the first place?

      n8n constructs its public-facing URLs (like for OAuth callbacks) using a combination of its environment variables: N8N_PROTOCOL, N8N_HOST, and N8N_PORT. With your current settings, n8n combines these and creates the URL: https://n8n.blorax.com:5678. This is the source of the incorrect callback URL.

      You’ve suggested changing N8N_PROTOCOL to http. This is a very logical thought process: Nginx talks to n8n over HTTP, so n8n’s internal protocol should be HTTP. However, there’s a subtle but important detail here.

      The N8N_PROTOCOL variable should reflect the protocol the end-user uses to access your site, not the internal protocol between the proxy and the application. This is because n8n needs to generate URLs that are correct for the outside world.

      Our current setup correctly handles this by using:

      • In the .env file: N8N_PROTOCOL=https and TRUST_PROXY_SSL_HEADERS=1
      • In the Nginx config: proxy_set_header X-Forwarded-Proto $scheme;

      This combination tells n8n: “Even though the connection directly to you is HTTP, trust the X-Forwarded-Proto header from my proxy (Nginx) to know the original protocol.” Since the original protocol is https, n8n correctly generates https:// links. This part of the configuration follows best practices.

      So, if we change N8N_PROTOCOL to http, n8n might start generating http:// links, which would cause other problems.

      The most reliable way to fix this is to explicitly tell n8n what its complete public base URL is, leaving no room for misinterpretation. This overrides the automatic URL construction from PROTOCOL+HOST+PORT.

      You do this with the N8N_EDITOR_BASE_URL environment variable. This variable is designed for precisely this scenario. It tells n8n, “Disregard any automatic URL building. For all editor-related links, including OAuth callbacks, use this exact base URL.”

      How to Implement the Fix

      1. Update your .env file:

      Ensure these lines are present and correct, pointing to your domain. The trailing slash is important.

      N8N_EDITOR_BASE_URL=https://n8n.blorax.com/
      WEBHOOK_URL=https://n8n.blorax.com/
      

      2. Update your docker-compose.yml file:

      Pass these variables from the .env file to the n8n container. This is the crucial step.

      services:
        n8n:
          # ... other settings like image, restart, ports ...
          environment:
            # ... other environment variables ...
            - N8N_EDITOR_BASE_URL=${N8N_EDITOR_BASE_URL} # <-- ADD THIS LINE
            - WEBHOOK_URL=${WEBHOOK_URL}                 # <-- AND THIS LINE
          # ... rest of the settings ...
      

      By setting N8N_EDITOR_BASE_URL and WEBHOOK_URL explicitly, you are giving n8n the exact, unambiguous public URLs for its operations. It will no longer try to add the internal :5678 port, and your OAuth callbacks will resolve to the correct, port-free URL: https://n8n.blorax.com/rest/oauth2-credential/callback.

      Hope this helps, and thank you again for the excellent contribution!

      July 29 at 16:08

Leave a Reply

Your email address will not be published. Required fields are marked *