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.
- 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 fileauthorized_keys
withnano
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.
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:
- 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.