Skip to content

Instantly share code, notes, and snippets.

@jjsquady
Last active September 19, 2024 06:16
Show Gist options
  • Save jjsquady/5399d6e1b23f501083a9c262d806e248 to your computer and use it in GitHub Desktop.
Save jjsquady/5399d6e1b23f501083a9c262d806e248 to your computer and use it in GitHub Desktop.
Deploying NEXTJS site with nginx + pm2

How to setup next.js app on nginx with letsencrypt

next.js, nginx, reverse-proxy, ssl

1. Install nginx and letsencrypt

$ sudo apt-get update
$ sudo apt-get install nginx letsencrypt

Also enable nginx in ufw

# after installing nginx!
$ sudo ufw allow 'Nginx Full'

2. Edit our default nginx site file

$ sudo vim /etc/nginx/sites-available/default
Content
# *q is our domain
server {
  listen 80 default_server;
  listen [::]:80 default_server;

  root /var/www/html;
  index index.html index.htm index.nginx-debian.html;

  server_name q*;

  location / {
    try_files $uri $uri/ =404;
  }
  
  # for letsencrypt
  location ~ /.well-known {
    allow all;
  }
}

Restart nginx

$ sudo nginx -t # check syntax errors
$ sudo systemctl restart nginx

3. Setup letsencrypt

# *q is our domain
$ sudo letsencrypt certonly -a webroot --webroot-path=/var/www/html -d *q -d www.q*

Generate Strong DH Group

$ sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 2048

Create nginx config file with Strong Encryption Settings

$ sudo vim /etc/nginx/snippets/ssl-params.conf
Content
ssl_protocols TLSv1.3; 
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;

resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

ssl_dhparam /etc/ssl/certs/dhparam.pem;

Edit our nginx file

# *q is our domain

# redirect http to https
server {
  listen 80 default_server;
  listen [::]:80 default_server;
  server_name *q www.*q;
  return 301 https://$server_name$request_uri;
}

server {
  # listen on *:443 -> ssl; instead of *:80
  listen 443 ssl http2 default_server;
  listen [::]:443 ssl http2 default_server;

  server_name q*;
  
  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
  include snippets/ssl-params.conf;

  location / {
    # reverse proxy for next server
    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;
  
    # we need to remove this 404 handling
    # because next's _next folder and own handling
    # try_files $uri $uri/ =404;
  }
  
  location ~ /.well-known {
    allow all;
  }
}

Restart nginx again

$ sudo systemctl restart nginx

4. Setup next.js app

$ yarn build # build our app for production (npm build script: next build)
$ yarn global add pm2 # install pm2 to keep next app alive forever*

# run start/stop
$ pm2 start npm --name "next" -- start # start next app (npm start script: next start)
$ pm2 stop next # for stopping app

We are done

Now you have next.js app up and running on nginx reverse proxy with ssl!

server {
server_name <domain_name>;
location / {
proxy_pass http://127.0.0.1:<PORT>;
proxy_read_timeout 60;
proxy_connect_timeout 60;
proxy_redirect off;
# Allow the use of websockets
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 /_next/static {
add_header Cache-Control "public, max-age=3600, immutable";
proxy_pass http://127.0.0.1:<PORT>/_next/static;
}
}
@mathieu-aubin
Copy link

Make up your mind, either you use Systemd or the deprecated 'service' command :)

@jjsquady
Copy link
Author

Make up your mind, either you use Systemd or the deprecated 'service' command :)

Fixed! Thank you.

@team172011
Copy link

Thanks for sharing this.

I had to add another location for the static contents:

location /_next/static {
        add_header Cache-Control "public, max-age=3600, immutable";
        proxy_pass http://127.0.0.1:8001/_next/static;
}

@AlejandroCosta02
Copy link

Hello, this guide help me a lot, but i am quite confused where do i need to clone the repository, or if that can change the result.

@Thinkscape
Copy link

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

This includes unsafe and deprecated protocols...
Only use v1.3, and only enable 1.2 if you know what you're doing.

@jjsquady
Copy link
Author

Thanks for sharing this.

I had to add another location for the static contents:

location /_next/static {
        add_header Cache-Control "public, max-age=3600, immutable";
        proxy_pass http://127.0.0.1:8001/_next/static;
}

Added this to site-nginx-config, thank you!

@jjsquady
Copy link
Author

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

This includes unsafe and deprecated protocols... Only use v1.3, and only enable 1.2 if you know what you're doing.

Updated ssl protocols section with only 1.3 version. Thank you

@mathieu-aubin
Copy link

When generating DH with openssl, adding the toggle -dsaparam to the command line makes the whole process go exponentially faster.

openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 2048

DH parameter generation with the -dsaparam option is much faster, and the recommended exponent length is shorter, which makes DH key exchange more efficient.

@jjsquady
Copy link
Author

When generating DH with openssl, adding the toggle -dsaparam to the command line makes the whole process go exponentially faster.

openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 2048

DH parameter generation with the -dsaparam option is much faster, and the recommended exponent length is shorter, which makes DH key exchange more efficient.

Updated with -dsparam option. Thank you!

@Gyalomalom
Copy link

Based

@tidosm
Copy link

tidosm commented Jun 19, 2023

Thanks for sharing this.
I had to add another location for the static contents:

location /_next/static {
        add_header Cache-Control "public, max-age=3600, immutable";
        proxy_pass http://127.0.0.1:8001/_next/static;
}

Added this to site-nginx-config, thank you!

Is this put in a separate file, or added to /etc/nginx/sites-available/default? Also, why is this on port 8001 instead of 3000?

@team172011
Copy link

Thanks for sharing this.
I had to add another location for the static contents:

location /_next/static {
        add_header Cache-Control "public, max-age=3600, immutable";
        proxy_pass http://127.0.0.1:8001/_next/static;
}

Added this to site-nginx-config, thank you!

Is this put in a separate file, or added to /etc/nginx/sites-available/default? Also, why is this on port 8001 instead of 3000?

@tidosm it was 8001 on my setup but should be 3000 in this example

@tidosm
Copy link

tidosm commented Jun 19, 2023

In the /etc/nginx/sites-available/default?

@antonmedv
Copy link

antonmedv commented Jul 6, 2023

Webpod - deploy JavaScript apps

BTW, I created a tool that automates installing software, configuring nginx and pm2, and automatic HTTPS: webpod

And it's just one simple command:

npx webpod example.com

@smeyerhot
Copy link

Webpod - deploy JavaScript apps

BTW, I created a tool that automates installing software, configuring nginx and pm2, and automatic HTTPS: webpod

And it's just one simple command:

npx webpod example.com

Interestng, will check this out!

@mfaridi1394
Copy link

What I must do when I want build of nextjs project build by yarn build can serve with Nginx without use proxy? I move to .next to nginx server and make config in /etc/nginx/conf.d for this project but I got 403 forbidden.

@Hugoqueiros
Copy link

But if I have 2 next.js apps running how can I distinguish the public and next folders?

@mathieu-aubin
Copy link

create a new nextjs app then create a new nginx config file with relevant changes to point to your new nextjs app and reload nginx...

@sc0rp10n-py
Copy link

i am uploading files in /public folder
now once the files are uploaded, i am forced to do pm2 restart for them to show up, is there a way to skip it?

I tried making a script that analyses changes in a folder and runs the command, but in that the pm2 is not able recognise the process id and name

maybe someway where I setup media/assets directory in nginx config?

@mathieu-aubin
Copy link

mathieu-aubin commented Oct 22, 2023

@sc0rp10n-py

i am uploading files in /public folder now once the files are uploaded, i am forced to do pm2 restart for them to show up, is there a way to skip it?

I tried making a script that analyses changes in a folder and runs the command, but in that the pm2 is not able recognise the process id and name

maybe someway where I setup media/assets directory in nginx config?

The way i do it is a bit different then this GIST but ultimately, does what you want it to do...
Instead of having nextjs location block under location / { in nginx, i change it for a named block which i call whatever i want... nextjs lets say.

Let's take the file that exist here and modify it to suit your needs.

    location / {
        root /var/www/PATH_TO_NEXTJS/public;
        try_files $uri @nextjs;
    }

    location @nextjs {
        proxy_pass http://127.0.0.1:<PORT>;
        proxy_read_timeout 60;
        proxy_connect_timeout 60;
        proxy_redirect  off;

        # Allow the use of websockets
        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;
    }

}

Basically

  • the / location block becomes a named block
  • a new block is created for / which sets the root folder to the NextJS public folder
  • that block contains a try_files directive which allows nginx to check if file exist, and serve it if yes, otherwise, goto the named location block (@nextjs) which then proxys as normal.

@goldjunge91
Copy link

Hi, your description was incredibly helpful; I spent two days searching for a straightforward solution, so thank you for sharing it. Where should I save the site-nginx-config, and do I need to make any changes to my Next.js project?

@montenegroPatrick
Copy link

Hi, thank you for sharing ! but i have a problem with the app-router of next 14, i see only my not-found page..
Do you know why ? maybe there is some config to add for next 14?

@shelllbyyyyyy
Copy link

Hey about for /api route handler
On my local mechine is work but on prod isn't

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment