Self host Next.js on Google Cloud VM for Free | Complete Guide

Learn how to deploy and self-host your Next.js app on Google Cloud Virtual Machine for free. Ubuntu VM, Ngnix and Node.js server to self-host Next.js

Aliaksandr TsykinAliaksandr Tsykin|
Self host Next.js on Google Cloud VM for Free | Complete Guide

Intro

If you are wondering: "How do I deploy and self-host my Next.js app for free?" - this tutorial was made for you.
This tutorial will guide you through the process of deploying and self hosting your Next.js app on Google Cloud Virtual Machine for free from zero to hero.

YouTube video tutorial

In case you prefer video tutorial, here’s the link to the video:

Loading Video...

Why self host Next.js on virtual machine?

Pros:

  • avoid vendor lock-in like Vercel or Netlify
  • it’s free (while using free tier VM)
  • full control over your machine and server
  • it performs better then some other solutions
  • you learn a lot while doing so
  • ability to scale (simply choose another machine)

Cons:

  • extra setup required (this tutorial is enough for basic setup though)
  • downtime while updating the app, meaning this solution might not be suitable for large projects, but OK for small projects with no need for 100% uptime
  • if you decide to upgrade your VM you will pay for machine all the time instead of pay-per-usage like Cloud Run

P.S. This blog is self hosted with Google Cloud VM for free. You can see for yourself how it performs.

Once you finish this tutorial, you will know how to use Google Cloud VM (VPS) with Ubuntu, Ngnix and Node.js to self host Nextjs for free.

Prerequisites

  • Cloud Cloud Platform Project with billing enabled
  • GitHub account (if you want to clone your app from GitHub)
  • Optional: Domain name

I use Hostinger to purchase and manage domain names.

1. Prepare Next.js app to be deployed

V1) Create your own app and make sure it’s ready to be cloned from GitHub

V2) Choose an app to clone from one of the Next.js templates from Vercel

I recommend starting with cloning one of these templates:

V3) Run npx create-next-app@latest on VM via SSH to create your. If you choose this option, you don't need GitHub repo. Just go to the next step.

2. Prepare SSH keys

Use this guide: How to generate SSH key

If you already have one - skip this step.

3. Create Google Cloud VM

Make sure to:

  • enable Compute Engine API before creating VM
  • choose free tier settings for VM
  • choose operating system compatible with control panel of choise (if you are planning to intall any). For example if you choose Hestia Control Panel, the system must be Ubuntu 20.04
  • Optional: Install Opt Agent for monitoring VM’s load (CPU, processes, memory, disk, netword etc)
If while enabling Compute Engine API you are stuck on an infinite loading, go to Billing > Link a billing account > Set account. You might have to request quota increase if you have multiple projects under same billing account. Source
To get Free VM on Google Cloud, choose following settings:
  • Name: any
  • Region: us-central1 (iowa)
  • Zone: any
  • Machine type: e2-micro (preset > shared core)
  • VM provisioning model: standard
  • Boot disk: Ubuntu 20.4 LTS (x86/64, amd64 focal image), Standard persistend disk, 30 GB
  • Firewall: Allow HTTP & HTTPS traffic
  • Networking > Network interfaces > Network Service Tier: Standard (us-central1)

After choosing settings as above, you should see that your Monthly estimate is around $7.31 (this might change in the future). In actuality you will not be charged this amount. Free credits will be used instead, since this is a free tier.

4. Add your SSH key to VM’s metadata

Google Cloud > Cloud Engine > Choose created VM > Settings > Metadata > SSH keys.

Metadata settings work for all intances inside a project, so if you have multiple VMs inside one project - you only have to add SSH key once.

5. Connect to VM via SSH

V1: Use Google Cloud Console > Cloud Engine > Choose created VM > Connect > SSH.

V2: Use any SSH client.

Command template: ssh -i shh/key/path username@hostname
Example command: ssh -i C:\Users\admin\.ssh\id_rsa robert@32.118.82.984

Command’s breakdown:

  • ssh: The command to initiate an SSH connection.
  • i shh/key/path: Specifies the path to the private key file.
  • username@hostname: Replace username with your remote username and hostname with the IP address or domain name of the server you want to connect to.
The username part of this command might just be a first part of your Gmail address.
For example if your gmail is robert@gmail.com, then username should be robert.

While running this command the first time, you might encounter an error:

"The authenticity of host can't be established.

This key is not known by any other names

Are you sure you want to continue connecting (yes/no/[fingerprint])?"
If you do - just type yes and hit enter.

6. Prepare VM and domain for Next.js deployment

6.1. [Optional] Add DNS to your domain provider

If you want to connect your domain to your VM, you need to add DNS records.

Record typeNameValueComment
A@VM external IP, example: 35.208.156.147Points domain to VM’s IP.
CNAMEwwwyourdomain.comEnables traffic from www.yourdomain.com
AadminVM external IP, example: 35.208.156.147[Optional] Points admin.yourdomain.com to VM’s IP.

6.2. Switch to superuser

Run sudo su -
You should see root@instance-name in terminal after that.

6.3. Install nvm, node and npm (if not installed)

Run following commands to install ...

nvm:

  • Check if node is installed: nvm -v
  • If not installed, run curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
Close and reopen terminal in order to use nvm
  • Check current nvm version: nvm -v

Node:

  • Install latest version of node.js: nvm install --lts
  • Check current node version: node -v

npm:

  • Install npm: sudo apt-get install npm (this might take a few minutes)
  • Check installed version of npm: npm -v

Don't worry if you get stuck on "Processing triggers for man-db ...". Just wait for it to finish.

6.4. Install git (if not installed)

It’s preinstalled on Google Cloud VM. Use which git to check if git is installed.

If not installed, run:

  • sudo apt-get update
  • sudo apt-get install git
  • which git

6.5. Clone repo from GitHub (if public)

Use this command: git clone https://github.com/user/repo-name.git

6.5. Clone repo from GitHub (if private)


6.5.1. Generate “Personal access token”. As a test you can give this token access to everything.

Don’t share this access token with anybody!

6.5.2. Modify and use this command on VM:

git clone https://<your_personal_access_token>@github.com/<user_name>/<repo_name>.git

Example:

git clone https://ghp_awpKsivWBSadwagTdacirasWDCCwacI@github.com/githubuser/test.git

6.5. Create next app

Run npx create-next-app@latest if you don't have an app that you want to deploy.

6.6. Try running starting the app

Before doing that it’s recommended to run hash -r

  • Go to project’s folder using cd foldername/
  • Run npm install (this might take a few minutes if you have a lot of dependencies)
  • Run npm run build (this might take 5-15 minutes)
  • Run npm run start

6.7. Setup firewall policies

In order to make sure that your app is accessible from outside, you need to open firewall for port 3000. Go to your VM’s settings > Firewall > Add firewall rule and create rule with following settings:

  • Name: allow-3000
  • Type: Ingress
  • Action: Allow
  • Targets: All instances in the network
  • Filters: IP ranges: 0.0.0.0/0
  • Protocol and ports: TCP:3000
After this rule is created, your app should be available at vm.ip.address:3000, for example 35.208.17.104:3000

You can also try stopping current process (ctrl + c) or close the terminal. After that the app should not be available, since we didn’t configure pm2 process to run even without active terminal.

7. Use pm2 to setup background processes

Currently if you close your terminal, the app stops working. In order to prevent this, we need to install pm2 and configure background process to run even when we don’t have active SSH terminal opened.

Let's configure pm2:
  1. Install pm2: sudo npm install -g pm2
  2. Go to project’s folder using cd appfoldername/
  3. Add & run “npm run start” process to pm2 with name “process-name”: pm2 start npm --name “process-name” -- start
  4. Check whether this process is running: pm2 status
  5. Configure pm2 to run this process on startup: pm2 startup
  6. Save this pm2 configuration: pm2 save

Additional info:

  • To stop this process, use pm2 stop processid or pm2 stop processname
  • To remove this process from reboot use pm2 unstartup systemd
  • To restart the process after it was stopped use pm2 restart processid or pm2 restart processname

8. Install and configure ngnix

In order for app to be available not only on port 3000, we need to install and configure ngnix

8.1. Configure your firewall settings:

Ensure that your Google Cloud VM firewall rules allow incoming traffic on both port 3000 and the default HTTP port (80). You may need to update your firewall rules to permit traffic on port 80.

Using Google Cloud Shell, run:

gcloud compute firewall-rules create allow-3000 --allow tcp:3000 --project <YOUR_PROJECT_ID>
gcloud compute firewall-rules create allow-80 --allow tcp:80 --project <YOUR_PROJECT_ID>
Instead of <YOUR_PROJECT_ID> insert project ID of your Google Cloud Project.

You can also create these rules manually via Google Cloud Dashboard.

8.2. Install ngnix

Make sure you are in root directory and run:

  • sudo apt-get update
  • sudo apt-get upgrade (this might take a few minutes)
  • sudo apt-get install nginx

8.3. Configure ngnix

  1. Edit the Nginx configuration to create a reverse proxy to your Next.js app.
Path that I will use for this tutorial: /etc/nginx/sites-available/nextjs.conf
Run: sudo nano /etc/nginx/sites-available/nextjs.conf and paste the following configuration.

Make sure to include your VM’s IP in this config file!

server {
  listen 80;
  # Include your VM external IP below
  server_name YOUR_SERVER_IP;

  location / {
    proxy_pass http://localhost:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
  }
}
  1. Create a symbolic link to enable the configuration:
  • Create symbolic link by running:
sudo ln -s /etc/nginx/sites-available/nextjs.conf /etc/nginx/sites-enabled/
  • Delete default ngnix config: rm /etc/nginx/sites-enabled/default
  • You can check whether that worked using ls /etc/nginx/sites-enabled/. This should only log file that you created.
  • Verify Symlink: ls -l /etc/nginx/sites-enabled/
  • Test ngnix config: sudo nginx -t
  1. Restart Nginx for the changes to take effect: sudo service nginx restart
  2. Check whether ngnix is working

Your app should now be available not only on port 3000, but also on your VM’s external IP (without port). If you configured DNS for your domain - your app should be avaiable under your domain as well.

Known errors

If while creating symbolic link you are running into this error:

ln: failed to create symbolic link '/etc/nginx/sites-enabled/nextjs.conf': File exists”

Tt means that you already have a config file in /etc/nginx/sites-enabled/.

To fix the issue delete current config file from /etc/nginx/sites-enabled/ using sudo rm /etc/nginx/sites-enabled/nextjs.conf and try againg.

Conflicting server name

After running sudo nginx -t you might get this error:
nginx: [warn] conflicting server name "35.208.197.116" on 0.0.0.0:80, ignored
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
If that's the case, delete the conflicting server name from /etc/nginx/sites-enabled/nextjs.conf and try again. You should only have one nextjs.conf file in /etc/nginx/sites-available/ directory.

9. Issue SSL certificate

  1. Install certbot:
  • sudo apt-get update
  • sudo apt-get install certbot python3-certbot-nginx
Check if certbot is installed: certbot --version
  1. Obtain a Certificate: sudo certbot certonly --nginx -d your_domain.com
Replace your_domain.com with your actual domain. This command will prompt you to enter an email address and agree to the terms of service.
  1. Update ngnix configuration
Run: sudo nano /etc/nginx/sites-available/nextjs.conf and paste the following configuration.

Make sure to include your VM’s IP and domain name in this config file!

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    # Replace YOUR_SERVER_IP with your VM's IP address
    server_name YOUR_DOMAIN_IP;

    # Configuration for SSL certificate
    # Replace YOUR_DOMAIN.NAME with your domain name
    ssl_certificate /etc/letsencrypt/live/YOUR_DOMAIN_NAME/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/YOUR_DOMAIN_NAME/privkey.pem;

    # Additional SSL configurations go here...

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

# Redirect HTTP traffic to HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name YOUR_DOMAIN_IP;

    return 301 https://$host$request_uri;
}
  1. Setup Automatic Certificate Renewal:

Let's Encrypt certificates expire after 90 days, so it's crucial to set up automatic renewal. Certbot can automatically renew certificates that are near expiry.

You can test the renewal process with: sudo certbot renew --dry-run
  1. Check Firewall Rules

Ensure that your firewall allows traffic on both port 80 and 443.

  1. Verify SSL Configuration:
Visit your site using https://yourdomain.com in a web browser to verify that the SSL certificate is recognized and that the connection is secure.

10. Enable guest attributes to VM’s metadata

Sometimes when leave your VM running overnight, the following might happen:

  • the app with stop working on IP and connected domain
  • SSH access will be unavailable

To avoid that, simply add the following key-value pair to VM’s metadata:

  • key: enable-guest-attributes
  • value: true

You can also simply restart the VM and it will (temporarily) fix the issue.

11. How to update the app once GitHub repo is updated?

While in app folder:

  1. stop pm2 process: pm2 stop process_id
  2. pull changes from remote origin: git pull
  3. rebuild your app: npm run build
  4. restart pm2 process: pm2 restart process_id

12. [Optional] How to rebuild app every day (update static content)

Following instruction is valuable for apps with a lot of static content that is often updated.

Note, that running this script will cause app downtime.


  1. Create a file (script) called update_and_restart.sh using nano update_and_restart.sh
  2. Add following code to this file:
#!/bin/bash

# Navigate to your project directory
cd path/to/your/nextjs/app

// Stop pm2 process
pm2 stop all

// Pull the latest changes from the GitHub repo
git pull origin main

// Install or update dependencies
npm install

// Build your Next.js app
npm run build

// Restart pm2 process
pm2 restart all
  1. Make the script executable by running chmod +x update_and_restart.sh
  2. Test the script by running ./update_and_restart.sh

This should execute the steps in your script and update your Next.js app.

  1. Schedule the Script with Cron
  • Open the crontab configuration by running: sudo crontab -e
  • Add the following line to schedule the script to run every day at 23:59: 59 23 * * * root/update_and_restart.sh

Now, your script will automatically run at 23:59 every day to stop pm2, pull the latest changes, build the app, and restart pm2.

13. [Pro] Use Remote SSH VS code extension to change files inside VM

  • Install VS Code extension called Remote - SSH from Microsoft
  • Open comand pallete using ctrl + shift + p and choose Remote SSH: Open SSH Config file and include the following:
Host your.server.ip
  HostName your.server.ip
  User user_name
  IdentityFile path_to_ssh_key

Here’s the example file:

Host 123.160.8.160
  HostName 123.160.8.160
  User username
  IdentityFile C:\Users\admin\.ssh\id_rsa
  • Then from command line choose Remote SSH: Connect to host
  • Open file explorer > Open folder > Choose a folder that contains your app

Now you can edit contents of your app in VS Code 🎊


Even though you are editing file that are on a VM, you can still open it onlocalhost:3001. To do that:
  • Open Terminal
  • Ports
  • Forward a port
  • Port: 3001, Forwarded address: localhost:3001
  • Open in browser

Known issues

npm install stuck on "reify:fsevents: timing reifyNode:node_modules/@img/sharp-darwin-"

While rinning npm install, stuck on "reify:fsevents: timing reifyNode:node_modules/@img/sharp-darwin-"

Solution - downgrade to node version 14:

  • npm install -g n
  • n v14

Source:

Quiz

Hopefully, you have a good understanding of how to deploy a Next.js app to a server. Now, let's have some fun and test your knowledge.

Can you deploy and self host a Next.js app on a virtual machine for free?

Do you need to have a domain name to deploy to a VM?

What do you need to install before building and deploying and self hosting your Next.js app on VM?

Resources used

Kudos to the following people/organisations for their valuable resources:

Table of Contents

Blog by Aliaksandr Tsykin

Learn web development and digital marketing with me!