n8n lets you orchestrate data and automate across tools. Self-hosting gives you control over data, cost, and extensibility, so you can tune performance, run custom code, and integrate private systems.
This step-by-step plan starts on Google Cloud’s free tier, then shows you how to put n8n behind HTTPS, keep data persistent, and upgrade safely.
1) Self-hosting n8n for free on Google Cloud
Google Cloud’s Always Free program includes one non-preemptible e2-micro VM per month in specific US regions, 30 GB-months of Standard persistent disk, and 1 GB of outbound data from North America per month. Eligible regions for the free e2-micro are us-west1 (Oregon), us-central1 (Iowa), us-east1 (South Carolina). The limit is by hours per month, combined across those regions.
Other hosting options: AWS, Hostinger, and DigitalOcean Droplets all work well. For this guide, we focus on Google Cloud’s free option to minimize cost.
Create the VM on Google Cloud
In the Google Cloud Console, create or select a project, enable Compute Engine, then click Create instance.
Use these settings to stay within free-tier limits: Region: us-west1, us-central1, or us-east1. Machine type: e2-micro. Boot disk: 30 GB Standard Persistent Disk. Firewall: check Allow HTTP and Allow HTTPS.
Reserve a static external IP, then assign it to the VM. Connect the IP with your domain’s A record in your registrar (for example n8n.example.com). SSH into the VM from the console to continue.
Advanced for tiny RAM: add a small swapfile
A modest swapfile can reduce out-of-memory crashes during spikes. It is not a replacement for RAM, it is a safety net.
# Create a 2G swapfile # Allocate 2G Swapfile sudo fallocate -l 2G /swapfile # secure permissions and make it a swap area sudo chmod 600 /swapfile sudo mkswap -f /swapfile # activate now sudo swapon /swapfile # verify free -h swapon --show
2) Docker, in plain English
Image: a packaged app plus all dependencies.
Container: a running instance of an image.
Volume: a folder on disk that survives restarts and upgrades.
3) Install Docker and test
Run these on the VM as a user with sudo.
# Patch the VM sudo apt-get update && sudo apt-get upgrade -y # Install Docker using the convenience script curl -fsSL https://get.docker.com | sudo sh # Allow your user to run docker without sudo (log out and back in after this) sudo usermod -aG docker $USER # Verify docker run hello-world docker version # Install Docker Compose plugin sudo apt-get install -y docker-compose-plugin docker compose version
4) Quick start: n8n with a single docker run
Create folders and an environment file for persistent settings.
sudo mkdir -p /opt/n8n /opt/n8n-media sudo chown -R $USER:$USER /opt/n8n /opt/n8n-media nano /opt/n8n/.env
Paste this minimal .env, then save.
# External URL for webhooks and redirects WEBHOOK_URL=https://n8n.example.com # Timezone GENERIC_TIMEZONE=America/Los_Angeles # Execution and storage NODE_ENV=production EXECUTIONS_MODE=regular EXECUTIONS_PROCESS=main N8N_DEFAULT_BINARY_DATA_MODE=filesystem N8N_BINARY_DATA_STORAGE_PATH=/media
Start n8n, bound to localhost so it is not exposed directly.
docker run -d --name n8n --restart unless-stopped --env-file /opt/n8n/.env -p 127.0.0.1:5678:5678 -v /opt/n8n:/home/node/.n8n -v /opt/n8n-media:/media n8nio/n8n:latest
Check that it is healthy.
docker ps curl -s http://127.0.0.1:5678/healthz
5) Add HTTPS with Nginx and a free TLS certificate
Install Nginx and Certbot.
sudo apt-get install -y nginx certbot python3-certbot-nginx
Create an Nginx site file.
sudo tee /etc/nginx/sites-available/n8n.conf >/dev/null <<'NGINX'
server {
listen 80;
server_name n8n.example.com;
location / {
proxy_pass http://127.0.0.1:5678/;
proxy_set_header Host $host;
proxy_set_header Origin $http_origin;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
NGINX
Enable and test.
sudo ln -s /etc/nginx/sites-available/n8n.conf /etc/nginx/sites-enabled/n8n.conf sudo nginx -t sudo systemctl reload nginx
Get a certificate and auto-enable HTTPS.
sudo certbot --nginx -d n8n.example.com --redirect -m you@example.com --agree-tos -n
Update .env so n8n knows its external URL is HTTPS, then restart n8n.
WEBHOOK_URL=https://n8n.example.com docker restart n8n
6) Docker Compose version for easier upgrades
Compose makes your setup repeatable.
mkdir -p /opt/n8n-compose && cd /opt/n8n-compose cp /opt/n8n/.env ./.env nano docker-compose.yml
docker-compose.yml:
services:
n8n:
image: n8nio/n8n:1.116.0
container_name: n8n
restart: unless-stopped
ports:
- "127.0.0.1:5678:5678"
env_file:
- .env
volumes:
- /opt/n8n:/home/node/.n8n
- /opt/n8n-media:/media
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:5678/healthz"]
interval: 30s
timeout: 5s
retries: 5
Start it.
docker compose up -d docker compose ps
Upgrade by changing the image tag, then:
docker compose pull docker compose up -d
Check that it is healthy.
docker ps curl -s http://127.0.0.1:5678/healthz
7) Basics of self-hosted n8n: environment, storage, and backups
Environment variables you will actually use
WEBHOOK_URL must match your public HTTPS URL so webhooks and redirects work.
GENERIC_TIMEZONE sets timestamps in the UI and logs.
N8N_DEFAULT_BINARY_DATA_MODE=filesystem moves large payloads out of the DB and onto disk at N8N_BINARY_DATA_STORAGE_PATH.
Storage with SQLite
The official image uses SQLite by default, stored under /home/node/.n8n. Because you mapped /opt/n8n:/home/node/.n8n, your database and credentials persist across restarts and upgrades. SQLite is perfect for small to medium workloads and single node setups, then you can migrate to Postgres later when you need more concurrency.
Back up regularly.
# Briefly stop to get a clean snapshot docker stop n8n tar -czf /opt/backups/n8n-$(date +%F).tar.gz /opt/n8n /opt/n8n-media docker start n8n
8) Troubleshooting
View logs: docker logs -f n8n
Health check: curl -s https://n8n.example.com/healthz
Free disk from old images: docker image prune -a and docker system prune -a
Patch the VM: sudo apt-get update && sudo apt-get upgrade -y
9) Updating n8n to the latest version
If you used a single docker run
Choose a specific version tag rather than latest, so you can roll back predictably. Replace X.Y.Z with the target version.
# Pull the new image docker pull n8nio/n8n:X.Y.Z # Stop and remove the running container docker stop n8n && docker rm n8n # Recreate with the same flags you used originally docker run -d --name n8n --restart unless-stopped --env-file /opt/n8n/.env -p 127.0.0.1:5678:5678 -v /opt/n8n:/home/node/.n8n -v /opt/n8n-media:/media n8nio/n8n:X.Y.Z # Verify docker logs --since=5m n8n | tail -n +1 # Rollback if needed docker stop n8n && docker rm n8n docker run -d ... n8nio/n8n:<previous-tag>
If you used Docker Compose
Edit docker-compose.yml and set the new tag for the image line, for example:
image: n8nio/n8n:X.Y.Z
Apply the update with minimal downtime:
docker compose pull docker compose up -d docker compose ps # Verify curl -s http://127.0.0.1:5678/healthz docker compose logs -f --since=5m
Rollback: restore the old tag in docker-compose.yml, then:
docker compose pull docker compose up -d
If you also run Runners
Update the Runners image tag in the same docker-compose.yml, then:
docker compose pull runners docker compose up -d runners