Hello everyone! In this tutorial, we are going to create a production-ready Next.js application using the app directory, Nginx, PM2, and Certbot to provide an SSL certificate and enable HTTPS. Let's get started!
Prerequisites
First of all, before we start, we will need a Linux-based server. In this tutorial, I will use Ubuntu 22, as it is a popular choice for many users and organizations with many advantages. You can use Digital Ocean, AWS, GCP, Hetzner, or any other cloud provider which offers free credits to get a server with a Linux distribution installed.
Next, we will need a domain name. I will use dev-soproniuk.com (which stands for my blog). However, this option is optional, and you can still deploy your application without a domain. I recommend Cloudflare as a domain registrar because it offers excellent CDN and protection features that you can use right away.
Finally, we will need the Next.js application itself. You can host your code on any popular repository hosting service, such as GitHub or GitLab, to easily make and pull modifications to the code.
Step 1. Installing Node.js, pnpm and Git
To run our Next.js application, we need to install Node.js. I recommend using nvm (Node Version Manager) to install the LTS version of Node.js.
Using apt for this purpose may result in installing an outdated version due to the way Debian and Ubuntu manage their repositories and packages..
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v{version}/install.sh | bash
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v{version}/install.sh | bash
Replace {version} with the latest version. For me, it was 0.39.7.
Then, run nvm ls-remote to view all available versions for installation. Choose the LTS version and install it. (For me, it was v20.15.1.)
nvm install 20.15.1nvm alias default 20.15.1node -v
After successfully installing Node.js with npm using nvm, let's install pnpm and pm2 using the npm package manager, which comes with Node.js by default.
npm install -g pnpm pm2pm2 --versionpnpm -v
Finally, let's install Git using apt. Before that, I'll update package index using the apt update command.
sudo apt updatesudo apt install gitgit --version
Step 2. Running Next.js application
We have already installed all the required packages to run our Next.js app. So, let's begin by cloning a repository and running it on the default port 3000. Later, we will restrict access to that port using the firewall. I will clone the Next.js Blog App repository as an example.
Inside /var/, I have created a new folder named www. Please navigate to that folder and clone the repository there.
git clone git@github.com:timtbdev/Next.js-Blog-App.git next-blog-appcd /var/www/next-blog-apppnpm install --prod
Note that some applications may require packages from dev dependencies. In such cases, use the pnpm install command without --prod.
After cloning and installing dependencies for our application, create a .env file if it is required. For simplicity, I will copy the contents from .env.example into .env.
Now, let's build our app.
pnpm run build
We have already installed pm2, so let's define a configuration file. I recommend using an ecosystem.config file for better organizational purposes, especially when managing multiple applications with PM2.
Let's create this file.
sudo nano ecosystem.config.js
Fill the file with the content below.
module.exports = {apps: [{name: 'site-name',cwd: '/var/www/site-path', // must have absolute pathscript: 'pnpm',args: 'run start',env: {NODE_ENV: 'production',},},],};
Change the name of your project to an appropriate one and update the 'cwd' parameter with the absolute path to your Next.js project.
Then, run the project.
pm2 start ecosystem.config.js
With that, our application is successfully running on port 3000. You can use the pm2 logs blog-app command to view logs for the blog-app application.
Step 3. Setup firewall rules
Our application is successfully running on port 3000. We can use the public IP address and port 3000 to access our application. However, before accessing the URL, we need to set up the firewall. Many cloud providers (such as AWS or GCP) block access to our server using their own firewall. In that case, we need to manually allow users to connect to the specified port. In my case, I will use ufw to block or allow connections to the server since I don't have a cloud-provided firewall.
First things first, I will enable ufw and add SSH and port 3000 to the allow list.
sudo ufw enablesudo ufw allow sshsudo ufw allow 3000sudo ufw status
We also need to open port 80 for HTTP and port 443 for HTTPS on our nginx server. You can use the following command to allow HTTP and HTTPS requests:
sudo ufw allow 'Nginx Full'
And to view all enabled rules, type the next command:
sudo ufw status
For now, we have successfully set up firewall rules for port 3000 and for HTTP/HTTPS. The next step is to install Nginx.
Step 4. Setup and configure Nginx for Next.js app
Let's start with installing Nginx.
sudo apt updatesudo apt install nginxsudo systemctl status nginx
The status should be active (running).
Next, let's create a configuration file for our website. I'm going to use the subdomain 'blog' of my main domain, dev-soproniuk.com, so I will name the file blog.dev-soproniuk.com
sudo nano /etc/nginx/sites-available/blog.dev-soproniuk.com
Apply the following configuration (redirecting to port 3000 on localhost with additional settings). Also, ensure the settings for the 'www' subdomain prefix are included.
server {server_name blog.dev-soproniuk.com;location / {proxy_redirect off;proxy_set_header Host $host;proxy_set_header Connection $http_connection;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 $scheme;proxy_read_timeout 1m;proxy_connect_timeout 1m;proxy_pass http://127.0.0.1:3000;}}# for wwwserver {server_name www.blog.dev-soproniuk.com;return 301 https://blog.dev-soproniuk.com$request_uri;}
Next, add a symbolic link to the sites-enabled directory to enable our site and check the configuration.
sudo ln -s /etc/nginx/sites-available/blog.dev-soproniuk.com /etc/nginx/sites-enabled/sudo nginx -t
Restart nginx if the configuration file and syntax are okay
sudo systemctl restart nginx
If the A-record was properly configured, our website should now be available using the HTTP protocol.
Finally, let's block port 3000 using the firewall command ufw delete."
sudo ufw status numberedsudo ufw delete 7
My port was numbered 7.
Step 4. Install and configure certbot for SSL encryption
Let's install Certbot and a package for Nginx.
sudo apt updatesudo apt install certbot python3-certbot-nginx
Afterward, we can apply that plugin to our domains.
sudo certbot --nginx -d blog.dev-soproniuk.com -d www.blog.dev-soproniuk.com
For the first time, you will be asked to enter an email address and agree to the terms of service. It will also prompt you to indicate whether you would like to receive updates on new products from the Let's Encrypt Foundation. Once completed, the certificates should be successfully installed.
You can also view Certbot's auto-renewal status by typing the command below.
sudo systemctl status certbot.timer
Or you can manually run renewal
sudo certbot renew --dry-run
Conclusion
In this tutorial, we successfully set up a production-ready Next.js application using Nginx, PM2, and Certbot on an Ubuntu 22 server. By following all the steps, you now have a secure, scalable, and maintainable Next.js application running in a production environment. This setup ensures your application is accessible via HTTPS and is efficiently managed using PM2 and Nginx.