next.js, nginx, reverse-proxy, ssl
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.
Make sure you enable port 443 (HTTPS) on your EC2 instance security group inbound traffic
Log into your EC2 instance connected to your elastic beanstalk environment
Type sudo -i
to be root user
Run
curl --silent https://raw.githubusercontent.com/srvrco/getssl/master/getssl > getssl ; chmod 700 getssl
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
- 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"
- 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"
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.
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";
}
}
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).
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.
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