Skip to content

Instantly share code, notes, and snippets.

@joelmasters
Forked from kocisov/next_nginx.md
Last active November 1, 2023 20:30
Show Gist options
  • Save joelmasters/b5c9de118f38191c9a77e3beaf3a0fac to your computer and use it in GitHub Desktop.
Save joelmasters/b5c9de118f38191c9a77e3beaf3a0fac to your computer and use it in GitHub Desktop.
How to setup next.js app on nginx with letsencrypt

How to setup next.js app on nginx with letsencrypt/getssl

next.js, nginx, reverse-proxy, ssl

Next.js package setup

Your server.js file should look like the following. Make sure you're serving static files!

const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');
const express = require('express');

const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
const port = process.env.PORT || 3000;

app.prepare().then(() => {
  const server = express();

  server.use(express.static('static', { dotfiles: 'allow' }));

  server.get('*', (req, res) => {
    const parsedUrl = parse(req.url, true);
    const { pathname, query = {} } = parsedUrl;
    return handle(req, res, parsedUrl);
  });

  server.listen(port, err => {
    if (err) throw err
    console.log('> Ready on http://localhost:' + port + '');
  })
})

Include a static folder in your package if it's not included already:

app
  server.js
  package.json
  /static
  /components

Upload .zip file to elastic beanstalk.

EC2 Side

Make sure you enable port 443 (HTTPS) on your EC2 instance security group inbound traffic

1. Log into EC2 instance using SSH

Log into your EC2 instance connected to your elastic beanstalk environment

Type sudo -i to be root user

2. Install getSSL

Run

curl --silent https://raw.githubusercontent.com/srvrco/getssl/master/getssl > getssl ; chmod 700 getssl

3. Get domain

Once you have obtained the script (see Installation above), the next step is to use

./getssl -c yourdomain.com

where yourdomain.com is the primary domain name that you want to create a certificate for. This will create the following folders and files.

~/.getssl
~/.getssl/getssl.cfg
~/.getssl/yourdomain.com
~/.getssl/yourdomain.com/getssl.cfg

4. Edit ~/.getssl/getssl.cfg

  • Use vim to edit (type: vim ~/.getssl/getssl.cfg)
  • Edit the file by typing 'i'
  • Comment out the first "CA=" line and uncomment the second
  • Change "me@example.com" to your email address in the ACCOUNT_EMAIL variable
  • Save the file by hitting 'Esc' and then typing ':x' and 'Enter'
# Uncomment and modify any variables you need
# The staging server is best for testing (hence set as default)
#CA="https://acme-staging.api.letsencrypt.org"
# This server issues full certificates, however has rate limits
CA="https://acme-v01.api.letsencrypt.org"

AGREEMENT="https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"

# Set an email address associated with your account - generally set at account level rather than domain.
ACCOUNT_EMAIL="me@example.com"
ACCOUNT_KEY_LENGTH=4096
ACCOUNT_KEY="/home/user/.getssl/account.key"
PRIVATE_KEY_ALG="rsa"

# The time period within which you want to allow renewal of a certificate - this prevents hitting some of the rate limits.
RENEW_ALLOW="30"

# openssl config file.  The default should work in most cases.
SSLCONF="/usr/lib/ssl/openssl.cnf"

5. Edit ~/.getssl/yourdomain.com/getssl.cfg

  • Uncomment second "CA=" line for the production server
  • Uncomment "ACL=" line and type in the root to your web server. For next.js app, I recommend /var/app/current/static/.well-known/acme-challenge (DONT FORGET TO ADD THE CLOSING ). For non-next app, it will be just /var/app/current/.well-known/acme-challenge
  • Uncomment and set USE_SINGLE_ACL to "true"
  • Uncomment DOMAIN_CERT_LOCATION
  • Uncomment DOMAIN_KEY_LOCATION
  • Uncomment DOMAIN_CHAIN_LOCATION and enter /etc/ssl/domain.chain.crt replacing "domain" with your domain
  • Uncomment SERVER_TYPE
  • Uncomment CHECK_REMOTE
# Uncomment and modify any variables you need
# see https://github.com/srvrco/getssl/wiki/Config-variables for details
# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs
#
# The staging server is best for testing
#CA="https://acme-staging.api.letsencrypt.org"
# This server issues full certificates, however has rate limits
CA="https://acme-v01.api.letsencrypt.org"

#AGREEMENT="https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf"

PRIVATE_KEY_ALG="rsa"

# Additional domains - this could be multiple domains / subdomains in a comma separated list
SANS="www.example.org"

# Acme Challenge Location. The first line for the domain, the following ones for each additional domain.
# If these start with ssh: then the next variable is assumed to be the hostname and the rest the location.
# An ssh key will be needed to provide you with access to the remote server.
# Optionally, you can specify a different userid for ssh/scp to use on the remote server before the @ sign.
# If left blank, the username on the local server will be used to authenticate against the remote server.
# If these start with ftp: then the next variables are ftpuserid:ftppassword:servername:ACL_location
# These should be of the form "/path/to/your/website/folder/.well-known/acme-challenge"
# where "/path/to/your/website/folder/" is the path, on your web server, to the web root for your domain.
ACL=('/var/app/current/static/.well-known/acme-challenge')
#     'ssh:server5:/var/www/${DOMAIN}/web/.well-known/acme-challenge'
#     'ssh:sshuserid@server5:/var/www/${DOMAIN}/web/.well-known/acme-challenge'
#     'ftp:ftpuserid:ftppassword:${DOMAIN}:/web/.well-known/acme-challenge')


# Location for all your certs, these can either be on the server (so full path name) or using ssh as for the ACL
DOMAIN_CERT_LOCATION="/etc/ssl/domain.crt"
DOMAIN_KEY_LOCATION="/etc/ssl/domain.key"
#CA_CERT_LOCATION="/etc/ssl/chain.crt"
DOMAIN_CHAIN_LOCATION="/etc/ssl/domain.chain.crt" this is the domain cert and CA cert
#DOMAIN_PEM_LOCATION="" this is the domain_key. domain cert and CA cert


# The command needed to reload apache / nginx or whatever you use
RELOAD_CMD="service nginx reload"

# Define the server type. This can be https, ftp, ftpi, imap, imaps, pop3, pop3s, smtp,
# smtps_deprecated, smtps, smtp_submission, xmpp, xmpps, ldaps or a port number which
# will be checked for certificate expiry and also will be checked after
# an update to confirm correct certificate is running (if CHECK_REMOTE) is set to true
SERVER_TYPE="https"
CHECK_REMOTE="true"

6. Get the domain

Next, run:

./getssl yourdomain.com

which should output similar to the following:

Registering account
Verify each domain
Verifing yourdomain.com
Verified yourdomain.com
Verifing www.yourdomain.com
Verified www.yourdomain.com
Verification completed, obtaining certificate.
Certificate saved in /home/user/.getssl/yourdomain.com/yourdomain.com.crt
The intermediate CA cert is in /home/user/.getssl/yourdomain.com/chain.crt
copying domain certificate to ssh:server5:/home/yourdomain/ssl/domain.crt
copying private key to ssh:server5:/home/yourdomain/ssl/domain.key
copying CA certificate to ssh:server5:/home/yourdomain/ssl/chain.crt
reloading SSL services

Hooray! You should now should have certificates.

7. Create configure nginx settings

Now, we need to actually implement the certificates.

Change to main directory cd ..

Create a new file /etc/nginx/conf.d/ssl_route.conf (touch /etc/nginx/conf.d/ssl_route.conf) and put the following code in it (vim /etc/nginx/conf.d/ssl_route.conf, 'i' to edit, replacing q* with your domain name in four locations:

server {
    listen 8080 default_server;
    listen [::]:8080 default_server;
    server_name q*;
    return 301 https://$server_name$request_uri;
}

server {

    listen       443 ssl http2 default_server;
    listen [::]:443 ssl http2 default_server;
    server_name  q*;

    error_page  497 https://$server_name$request_uri;

    if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2})") {
        set $year $1;
        set $month $2;
        set $day $3;
        set $hour $4;
    }
    access_log /var/log/nginx/healthd/application.log.$year-$month-$day-$hour healthd;
    access_log  /var/log/nginx/access.log  main;

    ssl_certificate      /etc/ssl/q*.chain.crt;
    ssl_certificate_key  /etc/ssl/q*.key;
    ssl_session_timeout  5m;
    ssl_protocols  TLSv1.1 TLSv1.2;
    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
    ssl_prefer_server_ciphers   on;

    location ~ /.well-known/  {
        allow all;
    }

    location / {
        proxy_pass  http://localhost:8081;
        proxy_set_header   Connection "";
        proxy_http_version 1.1;
        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        Upgrade         $http_upgrade;
        proxy_set_header        Connection      "upgrade";
   }    
}

8. Restart nginx

service nginx restart

Reload web page in browser. You may need to delete the cache and reload.

Note: if you see an error such as:

nginx: [emerg] could not build server_names_hash, you should increase server_names_hash_bucket_size: 64

edit /etc/nginx/nginx.conf and add the following to the top of the http block:

server_names_hash_bucket_size 64;

If still getting an error, increase by by another factor of 2 (128).

9. Renew Certificate

To renew a certificate, first login to EC2 instance, type sudo -i to become root user, cd .. to go to the root /. Type ./getssl -u -a -q. You should see something like: getssl: example.com - certificate obtained but certificate on server is different from the new certificate

If you see this, hooray! That was easy... Restart nginx using: service nginx restart. Reload web page. Check the certificate on the page by clicking the green lock -> certificate. Verify the date is current.

If that did not work, try the following:

Move the .well-known folder (and its sub-folder /acme-challenge) to /var/app/current/ if not there already (it was probably created in /var/app/current/public). To move: mv /var/app/current/public/.well-known /var/app/current

Open file /etc/nginx/conf.d/ssl_route.conf and comment out the location ~ /.well-known/ lines: (vim /etc/nginx/conf.d/ssl_route.conf, type i to edit in vim, and esc and then :x to exit and save). Make sure all of the q*'s below are replaced with your domain (they should be already)

server {
    listen 8080 default_server;
    listen [::]:8080 default_server;
    server_name q*;
    return 301 https://$server_name$request_uri;
}

server {

    listen       443 ssl http2 default_server;
    listen [::]:443 ssl http2 default_server;
    server_name  q*;

    error_page  497 https://$server_name$request_uri;

    if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2})") {
        set $year $1;
        set $month $2;
        set $day $3;
        set $hour $4;
    }
    access_log /var/log/nginx/healthd/application.log.$year-$month-$day-$hour healthd;
    access_log  /var/log/nginx/access.log  main;

    ssl_certificate      /etc/ssl/q*.chain.crt;
    ssl_certificate_key  /etc/ssl/q*.key;
    ssl_session_timeout  5m;
    ssl_protocols  TLSv1.1 TLSv1.2;
    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
    ssl_prefer_server_ciphers   on;

    # location ~ /.well-known/  {
    #     allow all;
    # }

    location / {
        proxy_pass  http://localhost:8081;
        proxy_set_header   Connection "";
        proxy_http_version 1.1;
        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        Upgrade         $http_upgrade;
        proxy_set_header        Connection      "upgrade";
   }    
}

Get the new certificate using: ./getssl -u -a -q. You should see something like: getssl: example.com - certificate obtained but certificate on server is different from the new certificate

Restart nginx using: service nginx restart. Reload web page. Check the certificate on the page by clicking the green lock -> certificate. Verify the date is current.

If renew still doesn't work:

vim /etc/nginx/conf.d/ssl_route.conf, update location section to the following:

  location ~ /.well-known/acme-challenge/  {
         allow all;
         root var/app/current;
  }

### References
See: https://github.com/srvrco/getssl and https://github.com/SammyHam/LetsEncrypt-SSL-config-for-Elastic-Beanstalk/blob/master/nginx_with_letsencrypt.conf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment