Guide on how to fully self-host n8n in a GCP project with up to no monthly costs (depending on the workflows you might pay networking costs, see: GCP Network Pricing) as well as auto-update the Docker image whenever the open-source GitHub repo of n8n has another release. The only two things you need to replicate this process 100% are a credit/debit card and a domain.
-
Go to cloud.google.com and click on Console
-
You need to add a billig method to verify your identity.
-
After you added your payment method and verified your identity you should click on Activate full account:
--> This allows you to use the account after the 90-day trial period expires. --> Bear in mind that after the trial period ends or if the credits are used up, you will be charged on the provided payment method. However, you can limit this risk by setting up budget alerts. Check Google Cloud Budgets.
-
Adding a New Project: If you are starting with a new Google Cloud account, it may take some time before you can create a new project. Alternatively, you can click on the settings of your existing "My First Project" and rename it for better association with your project.
-
Whichever way you are using it, when selecting your project in the top left, it should say, "You've activated your full account."
Now we set up the instance where N8N will run.
- Make sure your project is selected
- Click on VM Instances
- It might ask you to enable the Compute Engine API first (if you have just set up the account). Click "Enable" to proceed.
- Click on Create Instance
- In the configuration, you are generally free to set it up as you like. However, to host this instance for free, you need to check Google's requirements for the free-tier cloud setup: Google Cloud Free Tier.
As of now, the free-tier configuration is limited to:
- 1 non-preemptible e2-micro VM instance per month in one of the following US regions:
- Oregon:
us-west1
- Iowa:
us-central1
- South Carolina:
us-east1
- Oregon:
- 30 GB-months of standard persistent disk
- 1 GB of outbound data transfer from North America to all region destinations (excluding China and Australia) per month
- So, click on E2 and select the Preset.
Select e2-micro
- Click on OS and Storage, then select Change.
- Change Size (GB) to 30 and the Boot disk type to Standard Persistent Disk as per the guidelines.
- It will show you a monthly estimate, as running multiple instances 24/7 would incur these costs for what you selected. However, since you have only one project, you will stay within the free-tier limits, and the only potential costs will be for bandwidth, depending on your workflows.
- Give the instance a name (it doesn’t really matter—just make sure it's written without spaces) and click Create. Wait a minite until the Status says that it has compelted the setup. If it takes longer, refresh the page.
- Before setting up everything in the shell make sure to make the external IP static so that when an error occurs and you need to restart the instance it will still be pointing on your subdomain.
- click on VPC Network, IP addresses.
- It will show two IP addresses: one internal and one external. For the external IP, click on the three dots and select "Promote to static IP address.". Name again doesn't matter.
Lastly Select "Allow" on these 3 traffic sources. You can edit that later in the VM instance as well but we need it in order to reach our domain via our subdomain.
Now, go back to the VM instance we created and click on "Connect SSH."
Authorize with your Google Account (note that the connection may drop frequently). If that happens, just reconnect and reauthorize.
Depending on where you left off, you might need to reinstall or remove a partially installed instance. This happened to me quite often. In such cases, just ask ChatGPT how to uninstall the instance, and then you can restart the setup.
Now enter the following commands after one another:
- Update the Package Index:
sudo apt update
- Install Docker:
sudo apt install docker.io
Click on Y
- Start Docker:
sudo systemctl start docker
- Enable Docker to Start at Boot:
sudo systemctl enable docker
Now we want to add a subdomain of your domain which makes it easy to access your N8N instance from everywhere (dont worry you will need an account). With your domain provider go to Edit DNS settings (every domain provider has this) then you want to add a New Record.
copy the external IP address which we made static before:
For the new record, select Type A and name it whatever you like, but keep it short and precise. Points to: Paste the external IP address. TTL: Default is 14400; you can leave it as is.
Run the following command to start n8n in Docker. Replace your-domain.com with your actual domain name. Make sure you don’t copy and paste it with the "bash" part and the three backticks (```
), or it won’t work.
We are using a subdomain, it should look like this: (The subdomain is what we defined as the name—in my example, myn8n.)
```bash
sudo docker run -d --restart unless-stopped -it \
--name n8n \
-p 5678:5678 \
-e N8N_HOST="your-subdomain.your-domain.com" \
-e WEBHOOK_TUNNEL_URL="https://your-subdomain.your-domain.com/" \
-e WEBHOOK_URL="https://your-subdomain.your-domain.com/" \
-e N8N_ENABLE_RAW_EXECUTION="true" \
-e NODE_FUNCTION_ALLOW_BUILTIN="crypto" \
-e NODE_FUNCTION_ALLOW_EXTERNAL="" \
-e N8N_PUSH_BACKEND=websocket \
#-e N8N_DEFAULT_BINARY_DATA_MODE="filesystem" \ # Needed when e.g. trying to upload Youtube Videos
-v /home/your-google-account/.n8n:/home/node/.n8n \
n8nio/n8n
```
It now downloads the latest n8n image. Since this is the first installation, it obviously can’t find n8n:latest in your directory, so that’s not a problem.
We need Nginx as a reverse proxy to route traffic to n8n, handle SSL encryption, and allow access via a custom domain. Without it, n8n would only be reachable through its internal port (5678), which is not ideal for public access. An alternative is using a Google Cloud Load Balancer, but it’s more complex and can incur additional costs. Nginx is lightweight, free, and gives full control over traffic and security. It simplifies setup while ensuring a secure and accessible deployment.
- Install Nginx:
sudo apt install nginx
Click Y
- Configuring Nginx
Configure Nginx as a reverse proxy for the n8n web interface. This can be a bit tricky, so proceed carefully.
sudo mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled
Now edit the config file.
sudo nano /etc/nginx/sites-available/n8n.conf
Paste the following content (replace with your actual domain and subdomain):
server {
server_name your-subdomain.your-domain.com;
location / {
proxy_pass http://localhost:5678;
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;
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 https;
proxy_read_timeout 86400;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/your-subdomain.your-domain.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/your-subdomain.your-domain.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = your-subdomain.your-domain.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
server_name your-subdomain.your-domain.com;
return 404; # managed by Certbot
}
Save with Ctrl + O, Enter, then exit with Ctrl + X.
sudo ln -s /etc/nginx/sites-available/n8n.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
If the sudo nginx -t
test fails at this stage, it's because the Nginx configuration includes lines related to Certbot's SSL setup which hasn't run yet. You need to temporarily comment out these lines as shown below, run the test again, restart Nginx, and then proceed with Certbot.
server {
server_name your-subdomain.your-domain.com; # Replace with your subdomain and domain
location / {
proxy_pass http://localhost:5678;
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;
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 https;
proxy_read_timeout 86400;
}
# These sections are managed by Certbot and need to be commented out for the initial Nginx test
#listen 443 ssl; # managed by Certbot
#ssl_certificate /etc/letsencrypt/live/your-subdomain.your-domain.com/fullchain.pem; # managed by Certbot
#ssl_certificate_key /etc/letsencrypt/live/your-subdomain.your-domain.com/privkey.pem; # managed by Certbot
#include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
#ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
# This redirect is managed by Certbot and needs to be commented out for the initial Nginx test
#if ($host = your-subdomain.your-domain.com) { # Replace with your subdomain and domain
# return 301 https://$host$request_uri;
#} # managed by Certbot
listen 80;
server_name your-subdomain.your-domain.com; # Replace with your subdomain and domain
#return 404; # managed by Certbot - Also comment this out
}
Certbot will obtain and install an SSL certificate from Let's Encrypt.
- Install Certbot and the Nginx Plugin:
sudo apt install certbot python3-certbot-nginx
Click Y
- Obtain an SSL Certificate:
Here, we need to configure the firewall settings to allow HTTP and HTTPS traffic. If you haven't done this before, you can easily add firewall rules globally for the project or edit the VM instance settings.
Adjust the subdomain and domain.
sudo certbot --nginx -d myn8n.your-domain.com
Enter an Email and select Y Second one you can enter Y or N doesn't matter. It should work. If an error occurs it's propably due to the Firewall settings not being set up correctly.
When entering your domain (with the subdomain) in the browser, it should look like this:
Bear in mind that this has nothing to do with any n8n accounts you might already have. You are setting it up from scratch, and it will only work on this VM instance.
Since the official n8n repository regularly adds new features, it's important to stay updated without manually downloading, uploading workflows, or reconfiguring credentials. To automate this, we create a cronjob that checks for new stable releases (not pre-releases) in the n8n GitHub repository every Sunday night. If an update is available, it automatically updates the Docker image. Before updating, it saves your configurations in a new folder named update_n8n.
Bear in mind that the first part of your SSH prompt is your Google Account name, and the second part is the name of your VM instance.
For example, if your VM instance is called myvmn8n and your Google Account is johndoe@gmail.com, your SSH prompt will show:
johndoe@myvmn8n
After the updates execute it might show you errors: "Cannot GET /home " dont worry just dont let you browser auto-complete the URL. Enter subdomain.yourdomain.com then it will redirect you to the correct path.
nano /home/mygoogleaccount/update_n8n.sh
Add the following content:
#!/bin/bash
# Backup current n8n directory
BACKUP_DATE=$(date +'%Y-%m-%d_%H-%M-%S')
cp -r /home/mygoogleaccount/.n8n /home/mygoogleaccount/.n8n-backup-$BACKUP_DATE
# Stop and remove old container
sudo docker stop n8n
sudo docker rm n8n
# Pull latest n8n version
sudo docker pull n8nio/n8n:latest
# Start new container with correct volume
sudo docker run -d --restart unless-stopped -it \
--name n8n \
-p 5678:5678 \
-e N8N_HOST="myn8n.your-domain.com" \
-e WEBHOOK_TUNNEL_URL="https://myn8n.your-domain.com/" \
-e WEBHOOK_URL="https://myn8n.your-domain.com/" \
-e N8N_ENABLE_RAW_EXECUTION="true" \
-e NODE_FUNCTION_ALLOW_BUILTIN="crypto" \ # adding Javascript Package Crypto just to show how the packages would be added
-e NODE_FUNCTION_ALLOW_EXTERNAL="" \ # needed for external
-e N8N_PUSH_BACKEND=websocket \
-v /home/mygoogleaccount/.n8n:/home/node/.n8n \
n8nio/n8n
# Clean up old Docker images
sudo docker image prune -af
Save with Ctrl + O, Enter, then exit with Ctrl + X.
chmod +x /home/mygoogleaccount/update_n8n.sh
Open the crontab:
sudo crontab -e
(to be 100% sure open both the "sudo crontab -e" and the "crontab -e" and enter the same commands twice)
Select nano
Add this line (you can remove the comments before):
0 3 * * 0 /bin/bash /home/mygoogleaccount/update_n8n.sh >> /var/log/update_n8n.log 2>&1
30 3 * * 0 sudo find /home/mygoogleaccount/.n8n-backup* -maxdepth 0 -type d | sort | head -n -2 | sudo xargs rm -rf
@reboot sudo chown -R 1000:1000 ~/.n8n && sudo chmod -R 777 ~/.n8n
Save and exit.
0 3 * * 0 means:
- 0 → Minute (Runs at minute 0, i.e., the start of the hour)
- 3 → Hour (Runs at 3 AM)
*
→ Day of the month (Runs every day of the month)*
→ Month (Runs every month)0
→ Day of the week (0 = Sunday, 1 = Monday, ..., 6 = Saturday)
This schedule runs the script every Sunday at 3:00 AM.
Regarding the Update file deletion (-maxdepth 0 -type d | sort | head -n -2 | xargs rm -rf):
find /home/mygoogleaccount/.n8n-backup* -maxdepth 0 -type d
find
is the command to search for files/home/mygoogleaccount/.n8n-backup*
is the search pattern - it finds all directories that start with ".n8n-backup"-maxdepth 0
means that only the directly specified directories are searched, not in subdirectories-type d
finds only directories, not regular files
| sort
- The pipe (
|
) forwards the output of find to sort sort
arranges the list alphabetically, which for your backup names with dates is also chronological- e.g., ".n8n-backup-2025-02-15..." comes before ".n8n-backup-2025-03-09..."
- The pipe (
| head -n -2
- The pipe forwards the sorted list to head
head -n -2
takes all entries EXCEPT the last two- These last two are the newest backups (because of the previous sorting)
| xargs rm -rf
- The pipe forwards the directory names to be deleted to xargs
xargs rm -rf
executes the commandrm -rf
for each directory namerm -rf
deletes directories and all their contents recursively
-
Run the script manually:
/home/mygoogleaccount/update_n8n.sh
Since we just installed N8N it should not find a newer version so it should look like this:
-
Check backup directory:
ls /home/mygoogleaccount/ | grep .n8n-backup
-
Verify Docker volume binding:
docker inspect n8n | grep Mounts -A 10
Expected output:
"Source": "/home/mygoogleaccount/.n8n", "Destination": "/home/node/.n8n",
A backup of .n8n
is created before each update.
The container restarts with the old data preserved.
The cronjob automates updates every Sunday at 3 AM.
Now, please make sure that when setting up your private n8n account, you watch out for the correct path the next time you use it. It should be .../home
so that it doesn’t ask you to sign up again, as there is no login page from the signup screen. It may seem trivial, but if you don’t notice it, you might think your data is lost.
If the Cronjob does not work and it does not auto update enter:
sudo touch /var/log/update_n8n.log
sudo chmod 666 /var/log/update_n8n.log
this hands the log file the permissions it may need.
and change the Cronjob to:
0 3 * * 0 /bin/bash /home/stand_4_business/update_n8n.sh >> /var/log/update_n8n.log 2>&1
the "/bin/bash" tells him that he should execute it like that. Normally just entering the path to the file should work.
At worst case you can simply open the shell and enter it manually so it updates whenever you need it:
sudo bash /home/mygoogleaccount/update_n8n.sh
Hope this helps you set up and automate your n8n instance! 🚀 For more on how to effectively set up workflows that truly help you or your business be more efficient, check out our YouTube channel: StardawnAI. Thank you! 😊
After restarting the VM, n8n couldn't access its config due to permission issues which leads to a 502 BAD GATEWAY. Fix it with:
sudo chown -R 1000:1000 ~/.n8n
sudo chmod -R 777 ~/.n8n
sudo docker stop n8n
sudo docker rm n8n
sudo docker run -d --restart unless-stopped -it \
--name n8n \
-p 5678:5678 \
-e N8N_HOST="myn8n.my-domain.com" \
-e WEBHOOK_TUNNEL_URL="https://myn8n.my-domain.com/" \
-e WEBHOOK_URL="https://myn8n.my-domain.com/" \
-v ~/.n8n:/home/node/.n8n \
n8nio/n8n
sudo docker ps
docker ps
should showUp X minutes
curl
should returnHTTP/1.1 200 OK
or403 Forbidden
Open in browser:
https://myn8n.my-domain.com
crontab -e
Add the following line at the end to fix n8n folder permissions on startup:
@reboot sudo chown -R 1000:1000 ~/.n8n && sudo chmod -R 777 ~/.n8n
This ensures n8n always has full access to its config directory after a reboot.