01 Jun, 2022

Backend

Frontend

NextJS

Deploy

Ubuntu

How to deploy NextJS app on Ubuntu 22.04

In this article I’ll show how to deploy NextJS app on Ubuntu 22.04.

MROY Team

In this article I’ll show how to deploy NextJS app on Ubuntu 22.04.
NextJS allows to create fast, SEO-optimized ReactJS apps with easy configuration.

More on NextJS:
- https://youtu.be/Sklc_fQBmcs
- https://youtu.be/BILxV_vrZO0

Env:

Ubuntu 22.04
NodeJS 14
NPM 6
NextJS 12.1.6

Initial server setup

When you create a new Ubuntu server, you should make initial setup to increase security, and to make interaction with the server more comfortable.

Access the server using root user

ssh root@server_ip ssh is a utility for remote intercation with the server.
root is a default user that created in Ubuntu. It has all privileges and can make destructive changes to the system. Under root you can delete important system files, etc. So, it’s reasonable to create a user with shortened privileges for day-to-day administrative tasks.
server_ip is a server ip address.

Warning about host authenticity may appear:

The authenticity of host `server_ip` port `port` can't be established.
ECDSA key fingerprint is SHA256:blahblahblahblah.
Are you sure you want to continue connecting (yes/no)?  

Accept it. It’s a protection from man-in-the-middle attack.

More on host authenticity:  
- https://superuser.com/questions/1507193/what-does-this-ssh-message-mean-the-authenticity-of-host-cant-be-establis
- https://superuser.com/questions/421074/ssh-the-authenticity-of-host-host-cant-be-established

Create a new user

Let’s create a new user:

$ adduser deploy

“deploy” is a username. You can come up with your own.
You’ll be asked to enter a password.
The rest of the questions are optional. Just hit Enter.
You created a regular user account!

Sometimes you’ll need to do a task that require root privileges. You can achieve it prepending your command with sudo. To allow this, we need to add our user to sudo group.

To add user “deploy” to the sudo group:

$ usermod -aG sudo deploy

Now “deploy” will be able to run commands with root privileges.

More about sudo:  
https://serverfault.com/questions/601140/whats-the-difference-between-sudo-su-postgres-and-sudo-u-postgres

Add public key authentication

Key authentication is safer than password authentication. Let’s configure it.

Generate a key pair on your local machine

On your local machine enter:

$ ssh-keygen

You’ll see:

Generating public/private rsa key pair.
$ Enter file in which to save the key (/Users/username/.ssh/id_rsa):

It means you need to enter path and reasonable name for the file.
For example:

$ Enter file in which to save the key (/Users/username/.ssh/id_rsa): your_project_name_key

I think it’s reasonable to name your key the same as your project.

Next, you’ll be prompted to enter a passphrase:

$ Enter passphrase (empty for no passphrase): 
$ Enter same passphrase again: 

SSH passphrase protect your private key from being used by someone who doesn’t know the passphrase. It’s an additional layer of protection. You can leave it blank.

You’ll see private and public keys generated:

Your identification has been saved in your_project_name_key.
Your public key has been saved in your_project_name_key.pub.
The key fingerprint is:
SHA256:blahblahblah
The key's randomart image is:
+---[RSA 2048]----+
|       o=.       |
|    o  o++E      |
|   + . Ooo.      |
|    + O B..      |
|     = *S.       |
|      o          |
|                 |
|                 |
|                 |
+-----------------+
More about SSH encryption:
- https://www.digitalocean.com/community/tutorials/understanding-the-ssh-encryption-and-connection-process
- https://superuser.com/questions/383732/how-does-ssh-encryption-work
- https://www.ssh.com/academy/ssh/protocol
- https://phoenixnap.com/kb/how-does-ssh-work

More about SSH key randomart:  
- https://superuser.com/questions/22535/what-is-randomart-produced-by-ssh-keygen

Copy the public key

You need to place your public key on the server.
There is command ssh-copy-id for this:

$ ssh-copy-id -i ~/.ssh/your_project_name_key.pub deploy@server_ip
More about ssh-copy-id:  
https://www.ssh.com/academy/ssh/copy-id
`ssh-copy-id` is part of [OpenSSH](https://www.ssh.com/academy/ssh/openssh).

Actually, you can place your public key on the server manually.

$ cat ~/.ssh/your_project_name_key.pub
$ ssh deploy@server_ip
$ mkdir ~/.ssh
$ chmod 700 ~/.ssh
$ nano ~/.ssh/authorized_keys
$ chmod 600 ~/.ssh/authorized_keys

All these steps are done by ssh-copy-id automatically.

More about file permissions:  
- https://linuxize.com/post/understanding-linux-file-permissions/
- https://linuxfoundation.org/blog/classic-sysadmin-understanding-linux-file-permissions/
- https://www.tutorialspoint.com/unix/unix-file-permission.htm
- https://linuxcommand.org/lc3_lts0090.php
- https://wiki.archlinux.org/title/File_permissions_and_attributes

Disable password authentication

Let’s disable password authentication, so you’ll be able to the server with SSH key auth.

Edit sshd_config:

$ sudo nano /etc/ssh/sshd_config

sshd_config is a text file with configuration options of the SSH.

Read more about sshd_config:  
https://www.ssh.com/academy/ssh/sshd_config

Find the option PasswordAuthentication and change it value to “no”:

$ PasswordAuthentication no

Press CTRL + X, Y, ENTER to save and close the file.

Reload SSH daemon:

$ sudo systemctl reload sshd

Password authentication is now disabled. Now your server ca be accessed only with SSH key auth.

Bonus! Try to ssh into server with -v flag to see info about ssh process:

$ ssh deploy@server_ip -v
More about `systemctl`:  
- https://askubuntu.com/questions/903354/difference-between-systemctl-and-service-commands
- https://askubuntu.com/questions/795226/how-to-list-all-enabled-services-from-systemctl
- https://askubuntu.com/questions/tagged/systemd?tab=Votes

What is daemon:  
https://askubuntu.com/questions/26542/what-is-a-daemon

Add a basic firewall

Let’s set up UFW:

More about firewall:  
- https://ubuntu.com/server/docs/security-firewall

Configure a domain name

Buy domain name

There are plenty name registars, search the web and choose whatever you like.

How to buy domain name:
- https://mailchimp.com/resources/how-to-buy-a-domain-name/

Specify name servers

Name servers store DNS records which are used, among other things, to translate your domain into an IP address. For example, for DigitalOcean:

ns1.digitalocean.com
ns2.digitalocean.com
ns3.digitalocean.com

Another example are Cloudflare name servers:

eva.ns.cloudflare.com
kayden.ns.cloudflare.com

You may log into your domain name registar account and specify name servers you’d like to use (or you may use you registar’s nameservers as well). It will take some time (from a few minutes to a few days) for these changes to take effect.

More about DNS:  
- https://www.cloudflare.com/learning/dns/what-is-dns/
- https://www.cloudflare.com/learning/dns/what-is-1.1.1.1/

Set up DNS records for your domain

DNS records store different information about domain.
A record holds an IPv4 address of a domain. This will tell the browser where to look for your site.

More about DNS records:  
- https://www.cloudflare.com/learning/dns/dns-records/

Install and configure Nginx

Let’s setup Nginx.

Install Nginx

The apt-get command will get Nginx from Ubuntu repos and install it:

$ sudo apt-get update && sudo apt-get install nginx
More about apt-get:  
- https://en.wikipedia.org/wiki/APT_(software)
- https://linux.die.net/man/8/apt-get

More about Ubuntu repos:  
- https://help.ubuntu.com/community/Repositories/Ubuntu

Adjust firewall settings

Let’s get a list of the apps profiles:

$ sudo ufw app list
Available applications:
  Nginx Full
  Nginx HTTP
  Nginx HTTPS
  OpenSSH

Nginx Full opens both port 80 (unencrypted traffic) and port 443 (TLS/SSL encrypted traffic).
Nginx HTTP opens only port 80.
Nginx HTTPS opens only port 443.

Let’s allow Nginx HTTP and check ufw status:

$ sudo ufw allow 'Nginx HTTP'
$ sudo ufw status
More about ports:  
- https://www.cloudflare.com/learning/network-layer/what-is-a-computer-port/

More about ufw:  
- https://help.ubuntu.com/community/UFW
- https://www.digitalocean.com/community/tutorials/how-to-set-up-a-firewall-with-ufw-on-ubuntu-20-04

Test Nginx

Lets check Nginx status:

$ systemctl status nginx

If it’s active, try to acces your server by ip. In browser address bar:

http://your_server_ip

You should see default Nginx welcome page.

Configure Nginx as a reverse proxy

Let’s setup Nginx as a reverse proxy.

NextJS application will run on port 3000. It’s reasonable to setup Nginx as a reverse proxy for this port. Why it’s reasonable? It’s out of scope of this tutorial, but in short it’s good for security, performance, user experience, etc.

What is a proxy and a reverse proxy server?
- https://stackoverflow.com/questions/224664/whats-the-difference-between-a-proxy-server-and-a-reverse-proxy-server
- https://en.wikipedia.org/wiki/Reverse_proxy

Why it makes sense to use a reverse proxy in front of Node app?
- https://stackoverflow.com/questions/16770673/using-node-js-only-vs-using-node-js-with-apache-nginx

On Debian systems (Ubuntu “ancestor”) Nginx config are stored in /etc/nginx/sites-available. There is another folder, called sites-enabled, it contains symbolic links to easily toggle app’s status.
You should have file named default in /etc/nginx/sites-available.
Open it:

$ sudo nano /etc/nginx/sites-available/default

Read code, comments, and links.

You should look at the following URL’s in order to grasp a solid understanding of Nginx configuration files:
-  https://www.nginx.com/resources/wiki/start/
-  https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
-  https://wiki.debian.org/Nginx/DirectoryStructure

It’s a good practice to create a new config file for your site (named example.com instead of default). But for simplicity we’ll edit default in this tutorial.

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    # Specify root path
    root /home/deploy/your_project_name;

    # Add index.php to the list if you are using PHP
    index index.html index.htm index.nginx-debian.html;

    # Specify domain URL
    server_name yourproject.com www.yourproject.com;

    # Add reverse proxy config
    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;
    }
}

With this config, Nginx will server app running at local host port 3000.

You need to restart Nginx to apply the changes:

$ sudo nginx -t # to check config syntax
$ sudo systemctl restart nginx

Install NodeJS

You need NodeJS for NextJS app to run.

Install NodeSource PPA

You need access to PPA (Personal Package Archive) to install Node.
In your home directory use curl to get the installation script for the NodeJS 14.x:

$ curl -sL https://deb.nodesource.com/setup_14.x -o nodesource_setup.sh
$ sudo bash nodesource_setup.sh
$ sudo rm nodesource_setup.sh # won't need it anymore, so delete
$ sudo apt-get install nodejs # install NodeJS
$ sudo apt-get install build-essential # needed for some packages

You can check if node is installed by:

$ node --version
$ npm --version

Upload and configure NextJS app on the server

Upload your app files on the server.
You can do it via FTP client or ssh into server and clone your code from repo.

Get into app directory:

$ cd /home/deploy/your_project_name

Install NPM packages:

$ npm install

And build your app:

npm run build

This command will execute build script from package.json file (stored in the root of the project).
It may take some time. It’ll build production version of the app.

/.next directory will appear in the project directory. The production version of your app will use code from that directory.

To run production version, use:

npm start

If you done everything like in this tutorial you’ll be able to see your project online.
Copy-paste ip address of your server into search bar: http://http://127.0.0.1/ (it’s just an example).

But if we close terminal window or end process with ctrl + c our project won’t be available anymore.
To make our process persistent we can use PM2.

Install and configure PM2

PM2 is a process manager for NodeJS apps. It empowers you to keep your NodeJS apps alive forever, etc.

Install PM2

Install PM2 globally:

$ sudo npm install -g pm2

-g option tells NPM to install the package globally. It means the package will be available across entire OS.

Use PM2 to run the app

Let’s run the NextJS app with PM2 (make sure you are in your app dir):

pm2 start --name=your_project_name npm -- start

This command will start your NextJS app and display some info (project name, process ID, process status, etc).
PM2 will restart your app automatically if the app crashes, etc.
But additional configuration required to start app on OS reboot or boot.

Let’s repeat main steps:

  1. Upload source code to the server.
  2. npm install — install NPM dependencies.
  3. npm run build — run NextJS build script.
  4. pm2 restart your_project_name — to restart PM2 process.