Deploying Next.js Apps to Local Debian Server

To deploy my Next.js app to my local Debian server while still having my domain point to it, I had to first enable port forwarding on my router. I forwarded 80 to 80 and 443 to 443. Don't do what some guides say and do 80 to 3000, I can't remember why, but I think it had something to do with nginx.

I had to enable some ports through my ufw firewall, I don't really remember which. I think one was sudo ufw allow 'Nginx Full'. Then sudo ufw reload.

Speaking of, BEFORE STARTING THE NEXTJS app, go ahead and get nginx setup.

This is what my /etc/nginx/sites-available/nextjs-app looked like:

server {

    server_name mydomain.com www.mydomain.com;

    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }
}

Then link it with sudo ln -s /etc/nginx/sites-available/nextjs-app /etc/nginx/sites-enabled/. Test with sudo nginx -t. Restart with sudo systemctl restart nginx.

I made sure my DNS records were set up to point to my IP:

Then I ran sudo certbot --nginx.

This would automatically update my nginx file to accept SSL, but after that I changed it to work with Next.js. This is what it looked like after certbot and the NextJS addition:

server {

    server_name mydomain.com www.mydomain.com;

    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;
    }

    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/mydomain.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/mydomain.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot


}

Then when I had a built version of my app on the server, I ran pm2 start yarn --name "app_name" -- start from within the app directory. You can set it to always start on server boot with pm2 startup. Then save with pm2 save. Now I can do like pm2 start app_name, pm2 stop app_name, pm2 restart app_name, and pm2 reload app_name (last one is supposedly zero downtime).

After this, I was able to access my app at https://mydomain.com.

9.2.2024

I then wanted to deploy a second app to my server, using the subdomain example.anotherdomain.com.

In order to do this, I added a new A record in Cloudflare:

Host: example
Type: A
Data: MY_IP_ADDRESS

I cloned the repo I wanted to run to my server, and started it with:

pm2 start yarn --name "second_example_app" -- start
pm2 startup
pm2 save

Then I had to create a new Nginx server block:

sudo nano /etc/nginx/sites-available/example.anotherdomain.com

Where I put:

server {

    server_name example.anotherdomain.com;

    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }

    location / {
        proxy_pass http://localhost:3001;
        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;
    }

    listen [::]:443 ssl; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/example.anotherdomain.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/example.anotherdomain.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = example.anotherdomain.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    listen [::]:80;

    server_name example.anotherdomain.com;
    return 404; # managed by Certbot

Then I enabled the server block with:

sudo ln -s /etc/nginx/sites-available/example.anotherdomain.com /etc/nginx/sites-enabled

Tested with:

sudo nginx -t

Reloaded nginx:

sudo systemctl reload nginx

Got the SSL certificate:

sudo certbot --nginx -d example.anotherdomain.com

Restarted the app for good measure:

pm2 restart second_example_app

12.1.2024

I make no promises as to the security of either of these methods. If there are glaring vulnerabilities or easy ways to harden these, please shoot me an email at [email protected].