NextJS HTTPS/SSL Made Easy (Let's Encrypt Tutorial)

Updated

5 min read

Here's a quick and easy tutorial for setting up SSL for your NextJS app.

To host NextJS apps, I use Nginx server blocks and a reverse proxy.

Here's what this short tutorial will show you how to do:

  1. Configure your server block
  2. Serve ACME challenges
  3. Generate SSL certificates
  4. Add SSL to server block
  5. Auto-renewal

1. Configure your server block

Here's a basic set up for your Nginx server block you can copy.

# HTTP server block
server {
	listen 80 default_server;
	server_name example.com;

    # Serve ACME challenge static files
	location ~ /.well-known/acme-challenge {
		root /var/www/certbot/example.com;
		allow all;
	}

    # Redirect all other traffic to HTTPS
	location / {
		return 301 https://$server_name$request_uri;
	}
}

# HTTPS server block
server {
	listen 443 ssl;
	server_name example.com;

    # Uncomment the following lines after generating certificates
	# ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
	# ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Reverse proxy for NextJS app
	location / {
		proxy_pass 		http://127.0.0.1:3000; # Change this to your NextJS app port
        proxy_set_header 	Host $host;
        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      90;
	}
}

# HTTP server block for www.
server {
	listen 80;
	server_name www.example.com;

	location ~ /.well-known/acme-challenge {
		root /var/www/certbot/example.com;
		allow all;
	}

	location / {
		return 301 https://example.com$request_uri;
	}
}

# Redirect www to non-www
server {
	listen 443 ssl;
	server_name www.example.com;

    # Uncomment the following lines after generating certificates
	# ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
	# ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

	return 301 https://example.com$request_uri;
}

This configuration does the following:

  • Serves ACME challenge files for Let's Encrypt on HTTP
  • Redirects all other HTTP traffic to HTTPS
  • Sets up a reverse proxy for your NextJS app on HTTPS
  • Redirects www to non-www

Make sure to replace example.com with your domain name and 3000 with your NextJS app port.

Then you can run:

sudo nginx -t
sudo systemctl reload nginx

2. Serve ACME challenges

To generate SSL certificates, Let's Encrypt needs to verify that you own the domain using challenges.

The simplest and easiest challenge is the HTTP-01 challenge, which requires you to serve a specific file in a specific location on your server.

But since we're using NextJS, we'd normally serve files in the public directory, and this would mean rebuilding our NextJS app every time we need to renew our certificates.

Instead, we can direct the ACME challenge requests to a specific directory on our server.

cd /var/www # or any directory you prefer
mkdir certbot
cd certbot
mkdir example.com # make a separate directory for each domain

Looking back at our server block configuration, you can see that we set the root for the ACME challenge to /var/www/certbot/example.com.

Now, any files placed in /var/www/certbot/example.com will be served as static files by Nginx.

3. Generate SSL certificates

Using Let's Encrypt Certbot, we can now generate our SSL certificates.

Make sure certbot is installed:

bashsudo apt update
sudo apt install certbot

Then run the following command to generate your certificates:

bashsudo certbot certonly --webroot -w /var/www/certbot/example.com -d example.com -d www.example.com

This example is generating certificates for both example.com and www.example.com.

4. Add SSL to server block

After successfully generating our SSL certificates, we can update our server block:

# HTTP server block
server {
    ...
}

# HTTPS server block
server {
	listen 443 ssl;
	server_name example.com;

    # Now you can uncomment the following lines after generating certificates
	ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    ...
}

# HTTP server block for www.
server {
    ...
}

# Redirect www to non-www
server {
	listen 443 ssl;
	server_name www.example.com;

    # Now you can uncomment the following lines after generating certificates
	ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

	return 301 https://example.com$request_uri;
}

And lastly run:

sudo nginx -t
sudo systemctl reload nginx

5. Auto-renewal

Lastly, we just want to add a cronjob to automatically renew our certificates.

crontab -e

Then add the following line below the commented section:

48 2,14 * * * certbot renew --quiet --post-hook "systemctl reload nginx"

This will run every day at 2:48 AM and 2:48 PM to check if your certificates need to be renewed. If they do, it will renew them and reload Nginx.

Note:

It's recommended to choose a random minute and hour to avoid overloading Let's Encrypt servers. So instead of 48 2,14, you could choose something like 13 3,15 or 23 4,16.

Conclusion

And that's it! You should now have SSL set up for your NextJS app.

I hope this short tutorial was helpful. In my early days of NextJS and self-hosting, I was (naively) using DNS challenges to generate certificates, which was a bit of a hassle.

Serving ACME challenges as static files is a much simpler and more efficient way to handle SSL certificates for your NextJS apps.

Ryan Chiang

Meet the Author

Ryan Chiang

Hello, I'm Ryan. I build things and write about them. This is my blog of my learnings, tutorials, and whatever else I feel like writing about.
See what I'm building →.

Thanks for reading! If you want a heads up when I write a new blog post, you can subscribe below:

2024

2023

© 2023 Ryan Chiang|ryanschiang.com