HTTPS With Let's Encrypt, Nginx, And Docker
Suppose you have an Ubuntu instance (Google Compute Engine, Amazon EC2, etc.) running a web app via Nginx inside a Docker container and you would like the app to communicate over HTTPS.
There are numerous posts online on how to do this, usually using Nginx as a reverse proxy and Docker Compose. This is a powerful and general solution, but if you want a stopgap, read on.
Step 1
As Nginx is currently running inside a Docker container, it probably isn’t installed yet on the host instance itself
sudo apt-get update
sudo apt-get install nginx
Step 2
Let’s Encrypt is a free SSL/TLS certificate provider accepted by modern browsers for HTTPS communication. Certficates expire after three months and are renewable.
Installation and configuration is easiest via certbot
(follow the instructions under Install
).
Step 3
sudo certbot --nginx -d <mydomain>
replacing <mydomain>
with the domain your app is currently being served from:
Running this command will get a certificate for you and have Certbot edit your Nginx configuration automatically to serve it.
When prompted, select "redirect all HTTP requests to HTTPS"
.
At this stage, you can check everything works by starting Nginx (and stopping any processes running on ports 80
and 443
)
sudo systemctl start nginx
then navigating in a browser to <mydomain>
. You should see a padlock in the address bar and the default Debian-Nginx message.
After checking,
sudo systemctl stop nginx
For reference, these are the nginx configuration files produced by Certbot:
/etc/nginx/sites-available/default
##
# You should look at the following URL's in order to grasp a solid understanding
# of Nginx configuration files in order to fully unleash the power of Nginx.
# https://www.nginx.com/resources/wiki/start/
# https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
# https://wiki.debian.org/Nginx/DirectoryStructure
#
# In most cases, administrators will remove this file from sites-enabled/ and
# leave it as reference inside of sites-available where it will continue to be
# updated by the nginx packaging team.
#
# This file will automatically load configuration files provided by other
# applications, such as Drupal or Wordpress. These applications will be made
# available underneath a path with that package name, such as /drupal8.
#
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
##
# Default server configuration
#
server {
listen 80 default_server;
listen [::]:80 default_server;
# SSL configuration
#
# listen 443 ssl default_server;
# listen [::]:443 ssl default_server;
#
# Note: You should disable gzip for SSL traffic.
# See: https://bugs.debian.org/773332
#
# Read up on ssl_ciphers to ensure a secure configuration.
# See: https://bugs.debian.org/765782
#
# Self signed certs generated by the ssl-cert package
# Don't use them in a production server!
#
# include snippets/snakeoil.conf;
root /var/www/html;
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;
server_name _;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
# pass PHP scripts to FastCGI server
#
#location ~ \.php$ {
# include snippets/fastcgi-php.conf;
#
# # With php-fpm (or other unix sockets):
# fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
# # With php-cgi (or other tcp sockets):
# fastcgi_pass 127.0.0.1:9000;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# Virtual Host configuration for example.com
#
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
#
#server {
# listen 80;
# listen [::]:80;
#
# server_name example.com;
#
# root /var/www/example.com;
# index index.html;
#
# location / {
# try_files $uri $uri/ =404;
# }
#}
server {
# SSL configuration
#
# listen 443 ssl default_server;
# listen [::]:443 ssl default_server;
#
# Note: You should disable gzip for SSL traffic.
# See: https://bugs.debian.org/773332
#
# Read up on ssl_ciphers to ensure a secure configuration.
# See: https://bugs.debian.org/765782
#
# Self signed certs generated by the ssl-cert package
# Don't use them in a production server!
#
# include snippets/snakeoil.conf;
root /var/www/html;
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;
server_name <mydomain>; # managed by Certbot
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
# pass PHP scripts to FastCGI server
#
#location ~ \.php$ {
# include snippets/fastcgi-php.conf;
#
# # With php-fpm (or other unix sockets):
# fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
# # With php-cgi (or other tcp sockets):
# fastcgi_pass 127.0.0.1:9000;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/<mydomain>/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/<mydomain>/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 = <mydomain>) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80 ;
listen [::]:80 ;
server_name <mydomain>;
return 404; # managed by Certbot
}
/etc/letsencrypt/options-ssl-nginx.conf
# This file contains important security parameters. If you modify this file
# manually, Certbot will be unable to automatically provide future security
# updates. Instead, Certbot will print and log an error message with a path to
# the up-to-date file that you will need to refer to when manually updating
# this file.
ssl_session_cache shared:le_nginx_SSL:1m;
ssl_session_timeout 1440m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "xxxxxx";
/etc/letsencrypt/ssl-dhparams.pem
-----BEGIN DH PARAMETERS-----
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-----END DH PARAMETERS-----
Step 4
Assuming your app’s Dockerfile
contains a line similar to
COPY ./path/to/my/html /usr/share/nginx/html
then in /etc/nginx/sites-enabled/default
on the host instance, replace
root /var/www/html;
with
root /usr/share/nginx/html;
and set the server name
server_name <mydomain>;
Step 5
Add the below lines to the app’s Dockerfile
RUN addgroup -g 1000 -S www-data \
&& adduser -u 1000 -D -S -G www-data www-data
and rebuild the Docker image.
Step 6
Run the new Docker image, with some extra options:
--publish=443:443
to open up port 443
;
--volume=/etc/letsencrypt/:/etc/letsencrypt/
to get the certificates (and other required Let’s Encrypt files) on the host instance onto the container;
--volume=/etc/nginx/:/etc/nginx/
to ensure the container has the same Nginx configurations as the host instance.
The last option is why in Step 5
the www-data
user needs creating.
Step 7
Navigate in the browser to <mydomain>
. You should see your app running over
HTTPS.