Skip to content


HTTPS is now more important than ever. Strong encryption through HTTPS creates a safer and more secure web while protecting your site's users.

Roots believes in security so we've always made SSL/HTTPS a priority in Trellis. Our implementation is designed to score an A+ on the Qualys SSL Labs Test.

In the past many people avoided going HTTPS for technical and convenience reasons:

  • Certificates were expensive
  • Annoying and complicated web-server configuration
  • HTTPS sites were much slower than HTTP

Trellis has features to make it as easy, cheap, and painless as possible to use HTTPS giving you no excuse not to use it.

There are three supported certificate providers in Trellis:

HTTPS can be enabled on a per-site basis. However, by default, enabling SSL on a site will make that site HTTPS only. Meaning that all HTTP requests will be redirected to HTTPS with the proper HSTS headers set as well. Unless you have a good reason to change this default, you shouldn't. See the section on HSTS for more details.

CloudFlare Origin CA support can be added with trellis-cloudflare-origin-ca.

# Configuration

Any SSL provider starts with the same basic configuration. Add the following to a WP site:

# group_vars/production/wordpress_sites.yml (example)
  # rest of site config
    enabled: true
    provider: <name>

# Let's Encrypt

Let's Encrypt (LE) is a new Certificate Authority that is free, automated, and open.

Unless you already have an SSL certificate purchased, Let's Encrypt should be your provider choice. Let's Encrypt is appropriate for your production and staging environments, but not for development (see DNS records).

Trellis has complete automated integration. The only required setting is the provider itself:

# group_vars/production/wordpress_sites.yml (example)
  # rest of site config
    enabled: true
    provider: letsencrypt

There is one main difference between LE and other certificate authorities: their certificates expire every 90 days. Trellis automates by running a cron-job so you never have to manually renew them or worry about them expiring like a paid certificate.


Let's Encrypt is ending support for v1 of their ACME protocol. If you are using a Trellis version older than v1.2.0, please see here for more details.

# DNS records

Let's Encrypt verifies and creates certificates through a publicly accessible web server for every domain you want on the certificate.

This means you need valid and working DNS records for every site host/domain you have configured for your WP site.

# group_vars/production/wordpress_sites.yml (example)
    - canonical:
    enabled: true
    provider: letsencrypt

In the example above, Trellis will try to automatically create 1 certificate with the following hosts:,, and

All you need to do is make sure those DNS records exist and point to the web server's IP. Trellis takes care of the rest.

If you want "www" subdomains to redirect to your canonical domain, they MUST be included in redirects.

# Setting the contact email

LE requires at least one email address be provided as a contact email. Contact emails are used by LE to send expiry notices when a certificate is coming up for renewal.

# groups_vars/all/main.yml (example)

  - "{{ mail_admin }}" # defined in groups_vars/all/mail.yml

# Challenges

Let's Encrypt certificate process looks roughly like:

  1. Generate private account key
  2. Generate private key for each site (could have multiple domains)
  3. Generate CSR (Certificate Signing Request) for each site (single/multiple domains)
  4. Request certificate from LE by sending them the account key and CSR
  5. LE client creates a "challenge" file in the web root of your site
  6. LE server verifies it can access the challenge file
  7. LE server sends the certificate if the challenge succeeds

The above steps is what Trellis handles automatically.

# Multiple servers

Trellis' LE integration is designed by default for a single server. If you have multiple web servers behind a load balancer, you will not want this role/process running on all of them since it would generate different private and account keys for each one.

This process is beyond the scope of the documentation right now. However, there are two variables which help for this process:

  • letsencrypt_account_key_source_content
  • letsencrypt_account_key_source_file

You can use either of these to manually define an account key's contents or file. If one of these is set, it will be used and none will be automatically generated.

It's also up to you to make sure you've manually registered your account key. See for a simple site to do this.

# Staging

Let's Encrypt has rate limits for their production/real certificates. While Trellis will prevent these rate limits from being hit, if you want to test out LE integration, you can use their staging server to get a "fake" certificate.


Note that browsers will display an error/warning that they don't recognize the Certificate Authority so this should only be used for testing purposes.

Just set the following variable:

# in a group_vars file
letsencrypt_ca: ""

# Troubleshooting Let's Encrypt

Trellis versions prior to Jan 2017 did not detect some changes that should have triggered Let's Encrypt certificate regeneration. The most common example was users adding domain(s) to site_hosts (in wordpress_sites) and reporting that browsers gave privacy warnings for the new domains. Similar problems occurred for users switching from manual certificates to Let's Encrypt certificates.

If you see similar privacy warnings after adjusting your SSL configuration in some way, these troubleshooting steps may help.

  1. Update trellis to include roots/trellis#630
  2. Set ssl enabled: false for affected sites in group_vars/<environment>/wordpress_sites.yml
  3. Run ansible-playbook server.yml -e env=<environment> --tags wordpress
  4. Reset ssl enabled: true for applicable sites in group_vars/<environment>/wordpress_sites.yml
  5. Run ansible-playbook server.yml -e env=<environment> --tags letsencrypt

# Manual

This provider means you're providing both the SSL certificate and private key. This was the original method included in Trellis.

# group_vars/production/wordpress_sites.yml (example)
  # rest of site config
    enabled: true
    provider: manual
    cert: ~/ssl/
    key: ~/ssl/

cert and key are local relative paths to those files. They will be copied to the remote servers. This is done so your private key does not need to be stored in your Git repository for security reasons.

# Self-signed

The self-signed provider should only be used for development or internal server purposes. Trellis will generate a "fake" (or "snake-oil") certificate which is not recognized by browsers.

Browsers will prompt you with an error/warning that they don't recognize the Certificate Authority (which is yourself in this case).

# group_vars/development/wordpress_sites.yml (example)
  # rest of site config
    enabled: true
    provider: self-signed

You can tell your browsers to trust these self signed certificates by using the vagrant-trellis-cert plugin. This also fixes issues with MacOS Catalina where the 'certificate not trusted' error screens are not possible to bypass. From your trellis folder, run:

$ vagrant plugin install vagrant-trellis-cert
$ vagrant trellis-cert trust


Trellis sets HSTS headers for better security. HSTS will ensure all traffic to your site is being served over HTTPS automatically.

There are a few defaults set which you can override if need be:

  • hsts_max_age - how long the header lasts (default: 31536000 (1 year))
  • hsts_include_subdomains - also make all subdomains be served over HTTPS (default: false)
  • hsts_preload - indicates the site owner's consent to have their domain preloaded (default: false)

These variables are configured on a site's ssl object:

# group_vars/production/wordpress_sites.yml (example)
  # rest of site config
    enabled: true
    provider: letsencrypt
    hsts_max_age: 31536000
    hsts_include_subdomains: true
    hsts_preload: true

# Preload lists

What is HSTS Preloading?

HSTS Preloading is a mechanism whereby a list of hosts that wish to enforce the use of SSL/TLS on their site is built into a browser. This list is compiled by Google and is utilised by Chrome, Firefox, Opera, Safari, IE11 and Edge. These sites do not depend on the issuing of the HSTS response header to enforce the policy, instead the browser is aleady aware that the host requires the use of SSL/TLS before any connection or communication even takes place. This removes the opportunity an attacker has to intercept and tamper with redirects that take place over HTTP. This isn't to say that the host needs to stop issuing the HSTS response header, this must be left in place for those browsers that don't use preloaded HSTS lists.


Using preloading is a two-step process:

  1. Enable the preload option shown above by setting hsts_preload: true
  2. Submit your site/domain to the official browser preload list:

More information:


Trellis defaults to a long max-age of 31536000 seconds (1 year).

You may want to test out HSTS with much shorter max-ages and then ramp up the value in stages until you're confident everything works.

This deployment ramp up process is detailed here:

# Disabling HSTS

The only way to disable HSTS is to set the max-age header to 0:

# group_vars/production/wordpress_sites.yml (example)
  # rest of site config
    enabled: true
    provider: letsencrypt
    hsts_max_age: 0


HSTS should ideally be applied to all subdomains as well when possible. This means that if you have HSTS enabled on, then *all- its subdomains (* will also be forced over HTTPS.

However, it's a common enough scenario for a subdomain to host another non-HTTPS site for various reasons (maybe it's externally managed and out of your control). For example, you might deploy a Trellis based WordPress site to but also host another application from a subdomain such as which isn't secured by HTTPS.

Since HSTS' includeSubdomains option would break any subdomains in those situations, Trellis disables the hsts_include_subdomains option by default.

If you are in control of your domain and all its subdomains, we highly recommend you consider enabling the includeSubdomains option since it does provide stricter guarantees and security for your users.

This can be done by setting the hsts_include_subdomains option to true (either globally or a per-site basis).


# group_vars/production/wordpress_sites.yml (example)
  # rest of site config
    enabled: true
    provider: letsencrypt
    hsts_include_subdomains: true


# group_vars/production/main.yml

nginx_hsts_include_subdomains: true

# Client certificates

You may want to set up TLS Client Authentication, especially when using Cloudflare Authenticated Origin Pulls. To enable, simply set the client_cert_url variable to the URL of the certificate authority (CA) that will be used to verify clients with.

# group_vars/production/wordpress_sites.yml (example)
  # rest of site config
    # rest of ssl config

# Performance

Our HTTPS implementation uses all performance optimizations possible to ensure your sites remain fast despite the small overhead of SSL. This includes the following features:

  • HTTP/2 support (fallback to HTTP/1.1 for older browsers)
  • SSL session cache
  • OCSP stapling
  • 1400 byte TLS records
  • Longer keepalives

See Is TLS Fast Yet? for more information on fast TLS/SSL.

# Browser support

Since our implementation is designed to be modern and score an A+ on the Qualys SSL Labs Test, this does mean that a few older browsers such as IE6 won't be able to access your site due to the cipher suites used.


Last updated

Support Roots

Help us continue to build and maintain our open source projects. We’re a small team of independent developers and every little bit helps.

Sponsor Roots on GitHub