Route 53 Let’s Encrypt wildcard certificate with

How do I get a wildcard TLS/SSL certificate from Let’s Encrypt using and AWS Route53? How can I set up wildcard Let’s Encrypt SSL with AWS Route53 for Nginx or Apache?

For wildcard TLS/SSL certificates, the only challenge method Let’s Encrypt accepts is the DNS challenge to authenticate the domain ownership. Therefore, we need to Route53 AWS DNS API to add/modify DNS for our domain. This tutorial explains how to generate a wildcard TLS/SSL certificate using Let’s Encrypt client called running on Linux or Unix-like systems.
Tutorial requirements
Operating system/appLinux or Unix with AWS Route 53 DNS account
Root privileges required Yes
Difficulty Intermediate (rss)
Estimated completion time 20m
Table of contents

Prerequisite to set up Route 53 Let’s Encrypt wildcard certificate with

Make sure Nginx server installed and running. For example:
$ sudo apt install nginx
$ sudo yum install nginx

Apache users can run the following command::
$ sudo apt install apache2
$ sudo yum install httpd

Step 1 – Creating a new AWS user and get API access keys for Route 53

You can add user and create policy for Route53 using console. In this example, I will create a new IAM user for my AWS account, attach, and assign the policy using the aws cli.

Get the of hosted zones associated with the current AWS account

First we need to find out DNS zone ID. Run the aws command as follows to list hosted zone:
$ aws route53 list-hosted-zones
Note down your hosted zone Id. For example:

    "HostedZones": [
            "Id": "/hostedzone/RANDOM_ID_HERE_1",
            "Name": "",
            "CallerReference": "RISWorkflow-RD:473d5c18-2ca9-421b-b217-c40f9d90b976",
            "Config": {
                "Comment": "HostedZone created by Route53 Registrar",
                "PrivateZone": false
            "ResourceRecordSetCount": 2
            "Id": "/hostedzone/RANDOM_ID_HERE_2",
            "Name": "",
            "CallerReference": "2BC89E0B-FB84-7FA0-8EA6-5A2D46189415",
            "Config": {
                "Comment": "nixCraft forum DNS",
                "PrivateZone": false
            "ResourceRecordSetCount": 16

Create a new customer managed policy file named route53.txt

Use your favorite text editor such as vim to create a strict policy to update your Route53 DNS zone:
$ vim route53.txt
Append the following text replacing RANDOM_ID_HERE_2 as per your set up:

    "Version": "2012-10-17",
    "Statement": [
            "Sid": "vim0",
            "Effect": "Allow",
            "Action": [
            "Resource": "arn:aws:route53:::hostedzone/RANDOM_ID_HERE_2"
            "Sid": "vim1",
            "Effect": "Allow",
            "Action": "route53:ListHostedZones",
            "Resource": "*"

Creates police in AWS

The file route53.txt is a JSON document in the current folder (or /path/to/route53.txt) that grants read/write only access to the DNS zone in an Amazon Route 53 DNS ID named RANDOM_ID_HERE_2:
$ aws iam create-policy --policy-name le-route53-wildcard-dns-verification --policy-document file:///path/to/route53.txt

    "Policy": {
        "PolicyName": "le-route53-wildcard-dns-verification",
        "PolicyId": "AQPAZ4PKKZL7RYIBYM6YI",
        "Arn": "arn:aws:iam::791914887124:policy/le-route53-wildcard-dns-verification",
        "Path": "/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 0,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "CreateDate": "2020-08-03T08:14:25+00:00",
        "UpdateDate": "2020-08-03T08:14:25+00:00"

Please note down the Arn.

Create an AWS IAM account called “route53-dns-verification”

$ aws iam create-user --user-name route53-dns-verification

Assign AWS policy named “arn:aws:iam::791914887124:policy/le-route53-wildcard-dns-verification” to “route53-dns-verification” user account

$ aws iam attach-user-policy \
--policy-arn 'arn:aws:iam::791914887124:policy/le-route53-wildcard-dns-verification' \
--user-name route53-dns-verification

Create an access key for an IAM user named “route53-dns-verification”

You need to note down the AccessKeyId and SecretAccessKey after typing the following command. To ensure the security of your AWS account, the secret access key is accessible only during key and user creation. You must save the key if you want to be able to access it again. If a secret key is lost, you can delete the access keys for the associated user and then create new keys. Hence save them to a text file:
$ aws iam create-access-key --user-name route53-dns-verification

    "AccessKey": {
        "UserName": "route53-dns-verification",
        "AccessKeyId": "AKIZY3PTTYXXXXXXXXXX",
        "Status": "Active",
        "SecretAccessKey": "58XYYYYYYYYYYYYYYYYYYYYYYYYgmdS",
        "CreateDate": "2020-08-03T08:47:29+00:00"

Step 2 – Installing client

After getting Route53 API keys, now set up the client. Hence, clone the repo using the git command and then install the client using su command/sudo command $ cd /tmp/
$ git clone
$ sudo -i
# touch /root/.bashrc
# cd /tmp/
# --install --accountemail your-email-id@domain-here

Step 3 – Requesting new wildcard TLS certificate for domain using Route53 DNS

So far we set up Nginx/Apache, obtained Route54 API/access keys, and now it is time to use to get a wildcard certificate for domain. First set up the AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY using export command as follows:

Finally, request the wildcard based TLS/SSL certificate using Route53 dns as validator for your domain. Make sure you replace with your domain name:
# --issue --dns dns_aws --ocsp-must-staple --keylength 4096 -d -d '*'
Grab Elliptic-curve cryptography (ECC/ECDSA) instead of RSA certificate:
# --issue --dns dns_aws --ocsp-must-staple --keylength ec-384 -d -d '*'

  • --issue : Issue a certificate
  • --dns dns_aws : Use dns mode. In this case use AWS dns api.
  • -ocsp-must-staple : Generate ocsp must Staple extension.
  • --keylength ec-384 : Set the domain key length for ECC/ECDSA to ec-384. Please note that ec-521 currently not supported by the Let’s Encrypt.
  • --keylength 4096 : Set the domain key length for RSA.
  • -d -d '*' : Your domain name to issue, renew or revoke certificate.

Your Route 53 DNS API/access key is stored in /root/ file and we can see it using the cat command or grep command:
# cat /root/
# grep '_AWS_' /root/

Hence, do not share the /root/ file with anyone.

Step 4 – Configuring Nginx HTTPS

Make sure you create a Diffie-Hellman key exchange file as follows using the openssl command:
# mkdir -pv /etc/nginx/letsencrypt/${DOMAIN}/
# cd /etc/nginx/letsencrypt/${DOMAIN}/
# openssl dhparam -out dhparams.pem -dsaparam 4096

Then edit your Nginx config file or virtual domain file:
# vi /etc/nginx/vhosts.d/
Update/edit the file as follows:

# Port 80 config
server {
 listen      80 default_server; # IPv4
 listen [::]:80 default_server; # IPv6
 access_log  off;
 error_log   off;
 root        /var/www/html;
 return 301 https://$host$request_uri;
# Port 443 config
server {
 listen 443 ssl http2;                # IPv4
 listen [::]:443 ssl http2;           # HTTP/2 TLS IPv6
 server_name;  # domain name 
 root   /var/www/html;
 index  index.html;
 # Set access and error log for this vhos
 access_log /var/log/nginx/www.nixcraft.com_access.log;
 error_log  /var/log/nginx/www.nixcraft.com_error.log;  
 ssl_certificate /etc/nginx/ssl/letsencrypt/;
 ssl_certificate_key /etc/nginx/ssl/letsencrypt/;
 # ECC/ECDSA certificates (dual config)
 #ssl_certificate /etc/nginx/ssl/letsencrypt/;
 #ssl_certificate_key /etc/nginx/ssl/letsencrypt/;
 ssl_dhparam  /etc/nginx/ssl/letsencrypt/;
 # A little bit of optimization 
 ssl_session_timeout 1d;
 ssl_session_cache shared:NixCraftSSL:10m;
 # TLS version 1.2 and 1.3 only
 ssl_session_tickets off;  
 ssl_protocols TLSv1.2 TLSv1.3;
 ssl_prefer_server_ciphers off;  
 # HSTS (ngx_http_headers_module is required)
 # *************************************************************************
 # WARNING - Wrong headers can create serious problems. Read docs otherwise
 #           all 3rd party scripts/ads won't load and in some case 
 #           browser won't work. Read docs @
 # ************************************************************************* 
 add_header Strict-Transport-Security "max-age=63072000" always;
 add_header X-Content-Type-Options "nosniff" always;
 add_header X-Frame-Options "SAMEORIGIN" always;
 add_header X-Xss-Protection "1; mode=block" always;
 add_header Referrer-Policy  strict-origin-when-cross-origin always;
 add_header Feature-policy "accelerometer 'none'; camera 'none'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; payment 'none'; usb 'none'" always;
 # ***************************************************************************************************
 # WARNING: The HTTP Content-Security-Policy response header allows sysadmin/developers 
 # to control resources the user agent is allowed to load for a given page. 
 # Wrong config can create problems for third party scripts/ad networks. Hence read the following url: 
 # ****************************************************************************************************
 add_header content-security-policy "default-src" always;  
 # OCSP stapling
 # Verify chain of trust of OCSP response using Root CA and Intermediate certs
 ssl_stapling on;
 ssl_stapling_verify on;  
 ssl_trusted_certificate /etc/nginx/ssl/letsencrypt/;  
 # Replace with the IP address of your resolver

Step 5 – Installing certificate

Install the issued certificate to apache/nginx or any other server as per your set up. Make sure you replace the “/bin/systemctl reload nginx” as per your Linux/Unix distro:
# CONFIG_ROOT="/etc/nginx/ssl/letsencrypt/$DOMAIN"
# -d "$DOMAIN" \
--install-cert \
--reloadcmd "/bin/systemctl reload nginx" \
--fullchain-file "${CONFIG_ROOT}/$DOMAIN.fullchain.cer" \
--key-file "${CONFIG_ROOT}/$DOMAIN.key" \
--cert-file "${CONFIG_ROOT}/$DOMAIN.cer"

Install the ECC cert if you are using them too:
# -d "$DOMAIN" \
--ecc \
--install-cert \
--reloadcmd "/bin/systemctl reload nginx" \
--fullchain-file "${CONFIG_ROOT}/$DOMAIN.fullchain.cer.ecc" \
--key-file "${CONFIG_ROOT}/$DOMAIN.key.ecc" \
--cert-file "${CONFIG_ROOT}/$DOMAIN.cer.ecc"

Step 6 – Testing your nginx set up

Make sure you open Nginx server tcp port # 443 if not already opened. For example, here is how we can open it on Ubuntu or Debian Linux:
$ sudo ufw allow https comment 'Open all to access Nginx port 443'
Fire a web browser and type the url:
Of course we can visit SSL labs to test our TLS/SSL config page. Another option is to run the command as follows:
$ --fast --parallel

Wildcard SSL certs from Let’s Encrypt issued using and Route53 DNS.


There you have it, and we used and Route53 DNS to use the DNS challenge verification to obtain the certificates. You learned how to make a wildcard TLS/SSL certificate for your domain using and AWS Route53 DNS API for domain verification. Please note that automatically configure a cron jobs to renew our wildcard based certificate. You can now install certificates to ISP load balancer, Apache, Postfix smtpd, mysqld server or even use on LAN that are not open from the internet.

🐧 Please support my work on Patreon or with a donation.
🐧 Get the latest tutorials on Linux, Open Source & DevOps via:
CategoryList of Unix and Linux commands
File Managementcat
FirewallAlpine Awall CentOS 8 OpenSUSE RHEL 8 Ubuntu 16.04 Ubuntu 18.04 Ubuntu 20.04
Network Utilitiesdig host ip nmap
OpenVPNCentOS 7 CentOS 8 Debian 10 Debian 8/9 Ubuntu 18.04 Ubuntu 20.04
Package Managerapk apt
Processes Managementbg chroot cron disown fg jobs killall kill pidof pstree pwdx time
Searchinggrep whereis which
User Informationgroups id lastcomm last lid/libuser-lid logname members users whoami who w
WireGuard VPNAlpine CentOS 8 Debian 10 Firewall Ubuntu 20.04
8 comments… add one
  • DrinkWater Aug 3, 2020 @ 10:16


    Nice write up. Is it possible to restrict AWS custom policy to web server IPv4 address

    • 🐧 Vivek Gite Aug 3, 2020 @ 10:21
          "Version": "2012-10-17",
          "Statement": [
                  "Sid": "vim0",
                  "Effect": "Allow",
                  "Action": [
                  "Resource": "arn:aws:route53:::hostedzone/RANDOM_ID_HERE_2",
                  "Condition": {
                      "IpAddress": {
                          "aws:SourceIp": ""
                  "Sid": "vim1",
                  "Effect": "Allow",
                  "Action": "route53:ListHostedZones",
                  "Resource": "*",
                  "Condition": {
                      "IpAddress": {
                          "aws:SourceIp": ""

      Not tested it but it is doable with Condition

  • tom47 Aug 3, 2020 @ 10:27

    Jesus. Way too complicated with JSON crap. AWS overestimated and costly. I like using Digitalocean. Simple and easy to use. AWS bad. No wonder Linux people and developer think they are god with all this commands.

    • Go away Aug 4, 2020 @ 0:37

      Why bother commenting when you don’t have any knowledge about AWS? It is industry leader in cloud computing.

  • Jellyfish Aug 5, 2020 @ 4:35

    Why Certbot is much better client. Am I missing something?

    • tosh Aug 27, 2020 @ 12:03

      “” is POSIX shell script based AMCE client. Perfect for Docker or any other Linux containers tech. No need to install Python which is needed for Certbot.

  • TheDukeDK Aug 15, 2020 @ 6:56

    I made an example using Traefik and docker here:

    Which, I think, is a bit easier.

    But you also state in the conclusion “or even use on LAN that are not open from the internet.”

    I don’t understand this. With DNS-01 challenge LetsEncrypt verifies you are who you say you are with the DNS provider (route53 here). But the client ( in this case) has to retrieve it. So how could that be on a lan behind a firewall with no internet access?

    Also I am quite sure LetsEncrypt does NOT publish I.P. ranges which you can whitelist in your firewall.

    • 🐧 Vivek Gite Aug 15, 2020 @ 8:36

      I mean port 80/443 not opened or forwarded at the firewall level at the home server or dev workstation. I Will update the wording to make it clear. HTH

Leave a Reply

Your email address will not be published. Required fields are marked *

Use HTML <pre>...</pre> for code samples. Problem posting comment? Email me @