Assumptions / Pre-requisites
I'm assuming you have
- Created a clean EC2 instance running Ubuntu 24.04 with a public IP address
- Created a hosted zone in AWS Route53 for your domain name
- Configured your domain to use the name servers provided in the AWS Route53 zone file
- Added DNS entries for the apex domain and wildcard subdomain to point to your EC2 instance public IP address. For this post we'll assume we're setting up for the domain
supercoolwidgets.xyz
and the wildcard subdomain of*.supercoolwidgets.xyz
Setting up your server
Connect to your EC2 instance via its public IP address
Configure UFW to allow port 22, 80 & 443
# Add rules
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
# Check what's configured
sudo ufw show added
# Enable the firewall
sudo ufw enable
Install Docker
Set up Docker's apt repository.
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
Install docker packages
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Ref: https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository
AWS Permissions
Permission Policy
Create a permission policy (named webserver-caddy-route53
) to allow Caddy (/your EC2 instance) to update DNS records (required for wildcard DNS)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Action": [
"route53:ListResourceRecordSets",
"route53:GetChange",
"route53:ChangeResourceRecordSets"
],
"Resource": [
"arn:aws:route53:::hostedzone/... INSERT HOSTED ZONE ID HERE ...",
"arn:aws:route53:::change/*"
]
},
{
"Sid": "",
"Effect": "Allow",
"Action": [
"route53:ListHostedZonesByName",
"route53:ListHostedZones"
],
"Resource": "*"
}
]
}json
IAM Role
Create a role (named webserver-role
) and add the permission policy you just created above.
Edit the Trust Policy for your newly created webserver-role
as per the example below, substituting your role ARN in the AWS section below. The role ARN should be at the top of the page.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "... INSERT ROLE ARN HERE ...",
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EC2
Amend your EC2 instance and assign your webserver-role
as the IAM Role.
Setup your site
Project Folder
Create the folder to house your site
mkdir /opt/sites/supercoolwidgets.xyz
then go into this directory
cd /opt/sites/supercoolwidgets.xyz
Add a Caddy configuration file (Caddyfile
)
{
email your@email.address
}
supercoolwidgets.xyz, *.supercoolwidgets.xyz {
tls {
dns route53 {
profile "default"
}
}
root * /app/public
# Handle static files directly
file_server
# Symfony specific rewrites
try_files {path} {path}/ /index.html
}
Add an AWS config file (.aws/config
)
[profile default]
role_arn = ... INSERT ROLE ARN HERE ...
credential_source = Ec2InstanceMetadata
region = eu-west-2
Create a hello world page (public/index.html)
<html>
<body>
Hello World!
</body>
</html>
Folder structure
You should now have the following folder structure
|- .aws
|- config
|- Caddyfile
|- compose.yaml
|- public
|- index.html
Build the Caddy docker image with Route53 plugin
Add a docker file with the following contents
FROM caddy:2.9.1-builder-alpine AS caddy-builder
RUN xcaddy build v2.9.1 \
--with github.com/caddy-dns/route53@v1.5.1
FROM caddy:2.9.1-alpine AS app-caddy
COPY --from=caddy-builder /usr/bin/caddy /usr/bin/caddy
COPY Caddyfile /etc/caddy
mkdir /app/public
COPY public/ /app/public
Build the docker image and tag it with website:1.0.0
docker build -t website:1.0.0 .
Create a Docker compose file (compose.yaml)
name: website_prod
services:
caddy:
restart: unless-stopped
image: website:1.0.0
ports:
- 80:80
- 443:443
volumes:
- ./.aws:/root/.aws:ro
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:
Start your website
docker compose up -d
You should now be able to open the site in a browser and have it show the hello world! page, complete with a valid SSL certificate.
Note: The first time you open your site it will likely return an SSL error as it takes several seconds to get the SSL certificate initially. You can see what's happening by tailing the logs of your caddy container i.e.
docker logs -t ... CADDY CONTAINER NAME HERE ...
Originally published at https://chrisshennan.com/blog/wildcard-ssls-on-ec2-using-caddy-docker-aws-route53