Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Let's Encrypt Nginx setup with Ansible
Ansible playbook to setup HTTPS using Let's encrypt on nginx.
The Ansible playbook installs everything needed to serve static files from a nginx server over HTTPS.
The server pass A rating on [SSL Labs](https://www.ssllabs.com/).
To use:
1. Install [Ansible](https://www.ansible.com/)
2. Setup an Ubuntu 16.04 server accessible over ssh
3. Create `/etc/ansible/hosts` according to template below and change example.com to your domain
4. Copy the rest of the files to an empty directory (`playbook.yml` in the root of that folder and the rest in the `templates` subfolder)
5. Run `ansible-playbook playbook.yml`
6. Copy your (static HTML) code to `/var/www/example.com` (`example.com` replaced with your domain)
7. Restart nginx (`systemctl restart nginx`)
[letsencrypt]
example.com ansible_user=root letsencrypt_email=me@example.com domain_name=example.com
---
- hosts: letsencrypt
become: true
gather_facts: no
pre_tasks:
- raw: apt-get install -y python-simplejson
tasks:
- name: Upgrade system
apt: upgrade=dist update_cache=yes
- name: Install nginx
apt: name=nginx state=latest
- name: install letsencrypt
apt: name=letsencrypt state=latest
- name: create letsencrypt directory
file: name=/var/www/letsencrypt state=directory
- name: Remove default nginx config
file: name=/etc/nginx/sites-enabled/default state=absent
- name: Install system nginx config
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
- name: Install nginx site for letsencrypt requests
template:
src: templates/nginx-http.j2
dest: /etc/nginx/sites-enabled/http
- name: Reload nginx to activate letsencrypt site
service: name=nginx state=restarted
- name: Create letsencrypt certificate
shell: letsencrypt certonly -n --webroot -w /var/www/letsencrypt -m {{ letsencrypt_email }} --agree-tos -d {{ domain_name }}
args:
creates: /etc/letsencrypt/live/{{ domain_name }}
- name: Generate dhparams
shell: openssl dhparam -out /etc/nginx/dhparams.pem 2048
args:
creates: /etc/nginx/dhparams.pem
- name: Install nginx site for specified site
template:
src: templates/nginx-le.j2
dest: /etc/nginx/sites-enabled/le
- name: Reload nginx to activate specified site
service: name=nginx state=restarted
- name: Add letsencrypt cronjob for cert renewal
cron:
name: letsencrypt_renewal
special_time: weekly
job: letsencrypt --renew certonly -n --webroot -w /var/www/letsencrypt -m {{ letsencrypt_email }} --agree-tos -d {{ domain_name }} && service nginx reload
server_tokens off;
server {
listen 80 default_server;
server_name {{ domain_name }};
location /.well-known/acme-challenge {
root /var/www/letsencrypt;
try_files $uri $uri/ =404;
}
location / {
rewrite ^ https://{{ domain_name }}$request_uri? permanent;
}
}
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google-analytics.com; img-src 'self' data: https://www.google-analytics.com; style-src 'self' 'unsafe-inline'; font-src 'self'; frame-src 'none'; object-src 'none'";
# HTTPS server
#
server {
listen 443 ssl default deferred;
server_name {{ domain_name }};
ssl on;
ssl_certificate /etc/letsencrypt/live/{{ domain_name }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ domain_name }}/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/{{ domain_name }}/fullchain.pem;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 5m;
ssl_stapling on;
ssl_stapling_verify on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
ssl_dhparam /etc/nginx/dhparams.pem;
ssl_prefer_server_ciphers on;
root /var/www/{{ domain_name }};
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}
user www-data;
worker_processes 4;
pid /run/nginx.pid;
events {
worker_connections 768;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
gzip_disable "msie6";
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
@learnbybit

This comment has been minimized.

Copy link

@learnbybit learnbybit commented May 8, 2018

great ! thanks

@billmetangmo

This comment has been minimized.

Copy link

@billmetangmo billmetangmo commented Feb 5, 2019

+1

@SashaDesigN

This comment has been minimized.

Copy link

@SashaDesigN SashaDesigN commented Mar 2, 2019

GREAT work, thanks a lot)
Just note for whom it failed:
On Ubuntu you need to use certbot instead letsencrypt cli
And no need to use 2 websites to generate a certificate, it worked for me always with one, even when it's proxy and doesn't have no working directory to pass acme validation.

Also: letsencrypt made legacy one of methods for domains verification so make sure you're using certbot v0.28 and above

Happy coding)

@necrose99

This comment has been minimized.

Copy link

@necrose99 necrose99 commented Sep 19, 2019

:-) sweet

@anoopmd

This comment has been minimized.

Copy link

@anoopmd anoopmd commented Oct 13, 2019

Thank you so much !! You saved me so much time !!

@Prashanth-M

This comment has been minimized.

Copy link

@Prashanth-M Prashanth-M commented Oct 28, 2019

GREAT work, thanks a lot)
Just note for whom it failed:
On Ubuntu you need to use certbot instead letsencrypt cli
And no need to use 2 websites to generate a certificate, it worked for me always with one, even when it's proxy and doesn't have no working directory to pass acme validation.

Also: letsencrypt made legacy one of methods for domains verification so make sure you're using certbot v0.28 and above

Happy coding)

Can you share the command need to run in playbook for certbot (i'm using ubuntu)

@dupski

This comment has been minimized.

Copy link

@dupski dupski commented Nov 4, 2019

@diablozzq

This comment has been minimized.

Copy link

@diablozzq diablozzq commented Jan 2, 2020

This is amazing. Worked without any major modifications on ubuntu 19.10 on a raspberry pi.
I did the following changes

  • Changed hosts to localhost
  • Removed inventory file
  • Renamed the template files to fix their names after the download
  • Modified the SSL config to be a reverse proxy
  • Moved variables from inventory file into vars in the playbook
  • ran the playbook with ansible-playbook playbook.yml

My playbook:

---
- hosts: localhost
  become: true
  gather_facts: no
  vars:
    domain_name: www.example.com
    letsencrypt_email: example@example.com
  tasks:
    - name: Install nginx
      apt:
        name: nginx
        state: latest
    - name: install letsencrypt
      apt:
        name: letsencrypt
        state: latest
    - name: create letsencrypt directory
      file:
        name: /var/www/letsencrypt
        state: directory
    - name: Remove default nginx config
      file: 
        name: /etc/nginx/sites-enabled/default
        state: absent
    - name: Install system nginx config
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
    - name: Install nginx site for letsencrypt requests
      template:
        src: nginx-http.j2
        dest: /etc/nginx/sites-enabled/http
    - name: Reload nginx to activate letsencrypt site
      service:
        name: nginx
        state: restarted
    - name: Create letsencrypt certificate
      shell: letsencrypt certonly -n --webroot -w /var/www/letsencrypt -m {{ letsencrypt_email }} --agree-tos -d {{ domain_name }}
      args:
        creates: /etc/letsencrypt/live/{{ domain_name }}
    - name: Generate dhparams
      shell: openssl dhparam -out /etc/nginx/dhparams.pem 2048
      args:
        creates: /etc/nginx/dhparams.pem
    - name: Install nginx site for specified site
      template:
        src: nginx-le.j2
        dest: /etc/nginx/sites-enabled/le
    - name: Reload nginx to activate specified site
      service: name=nginx state=restarted
    - name: Add letsencrypt cronjob for cert renewal
      cron:
        name: letsencrypt_renewal
        special_time: weekly
        job: letsencrypt --renew certonly -n --webroot -w /var/www/letsencrypt -m {{ letsencrypt_email }} --agree-tos -d {{ domain_name }} && service nginx reload
@dcrystalj

This comment has been minimized.

Copy link

@dcrystalj dcrystalj commented Feb 26, 2020

@diablozzq this is great. what if need to handle 2 sub domains

example: domain_name: aaa.example.com and domain_name_2: bbbb.example.com

@googya

This comment has been minimized.

Copy link

@googya googya commented Jun 5, 2020

@diablozzq this is great. what if need to handle 2 sub domains

example: domain_name: aaa.example.com and domain_name_2: bbbb.example.com

you can do like this: -d www.aname.com -d api.aname.com -d api2.aname.com

also you can use wildcard way

@natehudson

This comment has been minimized.

Copy link

@natehudson natehudson commented Jul 31, 2020

Thanks for this!

FYI, on my Ubuntu 16.04.6 server with certbot 0.27.0, I had to change the cronjob from

job: letsencrypt --renew certonly -n --webroot -w /var/www/letsencrypt -m {{ letsencrypt_email }} --agree-tos -d {{ domain_name }} && service nginx reload

to

job: letsencrypt --renew-by-default certonly -n --webroot -w /var/www/letsencrypt -m {{ letsencrypt_email }} --agree-tos -d {{ domain_name }} && service nginx reload
@SzudemJ

This comment has been minimized.

Copy link

@SzudemJ SzudemJ commented Apr 2, 2021

Thank you man! This saved me hours!

@necrose99

This comment has been minimized.

Copy link

@necrose99 necrose99 commented Apr 3, 2021

pre_tasks:
- raw: apt-get install -y python-simplejson

for Gentoo replace

pre_tasks:
- raw: emerge -v dev-python/simplejson

@harshdevl

This comment has been minimized.

Copy link

@harshdevl harshdevl commented Aug 28, 2021

thanks man! amazing work.

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