AOG Poston
a software engineer

How to deploy NextJS app on Ubuntu 22.04
by AOGPoston on 12/05/23

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.

Requirements

  • 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

$ adduser deploy

Create a new user Let’s create a new user:

"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.

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.

  1. 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          | 
|                 | 
|                 | 
|                 | 
+-----------------+ 

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

"ssh-copy-id" is part of OpenSSH. Actually, you can place your public key on the server manually. Display your public key with:

cat ~/.ssh/your_project_name_key.pub

Select the public key and copy to the clipboard. Login to the server as "deploy" user:

ssh deploy@server_ip

Create a new directory called /.ssh:

mkdir ~/.ssh

Take care about permissions:

chmod 700 ~/.ssh

in /.ssh create a file authorized_keys with nano text editor:

nano ~/.ssh/authorized_keys

Insert the public key you generated and copied by pasting it into the nano editor. Press CTRL + X to exit the file, Y to save the changes, ENTER to confirm the file name. Take care about permissions:

chmod 600 ~/.ssh/authorized_keys

Close terminal and login as root user. All these steps are done by ssh-copy-id automatically. Disable password authentication Let’s disable password authentication, so you’ll be able to theserver 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. 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 ssh

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

Add a basic firewall

Let’s set up UFW

If your Ubuntu server has IPv6 enabled, ensure that UFW is configured to support IPv6 so that it will manage firewall rules for IPv6 in addition to IPv4. To do this, open the UFW configuration with nano or your favorite editor.

sudo nano /etc/default/ufw

Then make sure the value of IPV6 is yes. It should look like this:

...
# /etc/default/ufw excerpt
IPV6=yes

...

Let’s set your UFW rules back to the defaults so we can be sure that you’ll be able to follow along with this tutorial. To set the defaults used by UFW, use these commands:

sudo ufw default deny incoming
sudo ufw default allow outgoing

To configure your server to allow incoming SSH connections, you can use this command:

sudo ufw allow ssh

To enable UFW, use this command:

sudo ufw enable

You will receive a warning that says the command may disrupt existing SSH connections. You already set up a firewall rule that allows SSH connections, so it should be fine to continue. Respond to the prompt with y and hit ENTER.

Use this command to confirm all configurations:

sudo ufw status verbose

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

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

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?

Why it makes sense to use a reverse proxy in front of Node app?

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:

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 

If npm --version gives you an error like:


you might be able to resolve the issue by installing npm separately. Run:

sudo apt-get install npm

See (This thread)[https://stackoverflow.com/questions/31472755/sudo-npm-command-not-found ] for more ideas.

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:
Upload source code to the server. npm install — install NPM dependencies. npm run build — run NextJS build script. pm2 restart your_project_name — to restart PM2 process.

Generate a key and add it to keychain: https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent

How to add github to your keychain on ubuntu: https://stackoverflow.com/questions/13363553/git-error-host-key-verification-failed-when-connecting-to-remote-repository

ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts