The short answer: if your site already sits behind an Application Load Balancer or CloudFront, use a free certificate from AWS Certificate Manager (ACM). AWS renews it for you, and you never touch a certificate file.
If traffic goes straight to the instance, install a free Let's Encrypt certificate with Certbot. Buy a paid certificate only when a contract, a compliance policy or a client asks for OV or EV validation.
Whichever you choose, plan for renewal from day one. Public certificates are now valid for 200 days at most, and that limit drops to 100 days in March 2027.
A note on names: "SSL certificate" is just the common term. The protocol itself is TLS, and every setup in this guide allows only TLS 1.2 and TLS 1.3. SSL 3.0, TLS 1.0 and TLS 1.1 are formally deprecated, and modern browsers refuse them. Security scans and compliance audits flag a server that still accepts them.
Which option to choose
- ACM certificate on an ALB or CloudFront. Free, renews automatically, no files on the server. The catch: a regular ACM certificate can't be installed on the instance itself. It only works on AWS services like Elastic Load Balancing, CloudFront and API Gateway. An ALB also adds an hourly charge, which matters for a single small instance.
- Let's Encrypt on the instance. Free, and Certbot renews it automatically. Best for one instance with a public IP and Nginx or Apache on it.
- A paid OV or EV certificate on Nginx. You pay a CA such as Sectigo (formerly Comodo CA), DigiCert or GlobalSign to check your organization, not just the domain. You get the same padlock in the browser, but you install and renew the certificate yourself.
ACM has two newer options for certificates that live on the instance.
Exportable public certificates give you the certificate and private key to install anywhere. ACM renews them, but you still deploy each renewal yourself. ACM also runs an ACME endpoint that works with Certbot and issues 45-day certificates.
Both are paid per certificate (see ACM pricing). They make sense for teams that want every certificate in one ACM inventory. For a single site, Let's Encrypt does the same job for free.
How long SSL certificates last now
The CA/Browser Forum, the group of CAs and browser makers that sets the rules for public certificates, passed ballot SC-081v3 in April 2025. It cuts the maximum validity of public TLS certificates in steps:
- issued before March 15, 2026: up to 398 days;
- issued from March 15, 2026: up to 200 days;
- issued from March 15, 2027: up to 100 days;
- issued from March 15, 2029: up to 47 days.
The period for reusing a domain check shrinks the same way, down to 10 days in 2029. Some CAs stay a day under each limit: DigiCert issues 199-day certificates now.
What this means for you:
- A "2-year" or "3-year" certificate is a subscription now. You pay once, but the CA issues a new certificate every few months, and you have to install each one.
- Manual renewal every few months is easy to forget. One missed renewal and visitors see a full-page certificate error. ACM and ACME clients like Certbot handle renewal for you, which is why they're the default choice now.
- ACM certificates already follow the new rules. They're valid for 198 days, and ACM starts renewing them 45 days before they expire.
Option 1: ACM certificate on an Application Load Balancer
With this setup, the ALB terminates HTTPS and passes plain HTTP to your instance on port 80. The certificate never leaves AWS.
The commands below use the AWS CLI. You can do the same in the ACM and EC2 consoles.
Step 1: Request a certificate in ACM
Request the certificate in the same region as your load balancer. For CloudFront, request it in US East (N. Virginia), us-east-1.
shell
1aws acm request-certificate \2 --domain-name example.com \3 --subject-alternative-names www.example.com \4 --validation-method DNSThe command returns the certificate ARN. Get the DNS records ACM wants you to create:
shell
1aws acm describe-certificate \2 --certificate-arn <certificate-arn> \3 --query "Certificate.DomainValidationOptions[].ResourceRecord"Add each CNAME record to your DNS zone. If the domain is in Route 53, the ACM console has a "Create records in Route 53" button that does it for you.
The status changes to "Issued" once ACM sees the records. Keep the CNAME records in place after that. ACM uses them to renew the certificate automatically.
Step 2: Add an HTTPS listener to the load balancer
If you don't have an ALB yet, create one with a target group that sends traffic to your instance on port 80. Then add an HTTPS listener with the certificate:
shell
1aws elbv2 create-listener \2 --load-balancer-arn <alb-arn> \3 --protocol HTTPS --port 443 \4 --ssl-policy ELBSecurityPolicy-TLS13-1-2-2021-06 \5 --certificates CertificateArn=<certificate-arn> \6 --default-actions Type=forward,TargetGroupArn=<target-group-arn>ELBSecurityPolicy-TLS13-1-2-2021-06 allows TLS 1.2 and TLS 1.3 only, and it's the policy from the AWS docs on HTTPS listeners.
Step 3: Redirect HTTP to HTTPS on the load balancer
Make the port 80 listener answer with a redirect instead of forwarding to the instance. If you already have an HTTP listener, change its default action in the console to "Redirect to URL" with HTTPS, port 443 and status code 301. For a new listener:
shell
1aws elbv2 create-listener \2 --load-balancer-arn <alb-arn> \3 --protocol HTTP --port 80 \4 --default-actions '[{"Type":"redirect","RedirectConfig":{"Protocol":"HTTPS","Port":"443","StatusCode":"HTTP_301"}}]'The ALB keeps the host, path and query string, so a request to http://example.com/blog/?page=2 goes to https://example.com/blog/?page=2.
Step 4: Close the instance to direct traffic
The load balancer's security group should allow ports 80 and 443 from anywhere. The instance's security group should allow port 80 only from the load balancer's security group, not from 0.0.0.0/0. That way, nobody can bypass HTTPS by going to the instance's IP address.
Don't add an HTTP to HTTPS redirect in Nginx on the instance. Nginx only sees plain HTTP from the ALB, so it would redirect every request and you'd get a redirect loop.
If your app needs to know the original scheme, read the X-Forwarded-Proto header. Our guide on redirecting HTTP to HTTPS with Nginx shows the fix for that loop.
Renewal: nothing to do. ACM renews the certificate and the load balancer picks up the new one.
Option 2: Let's Encrypt on the instance
If visitors connect straight to the instance, a free Let's Encrypt certificate is the simplest choice. Certbot gets the certificate, updates the Nginx config and renews it on a timer.
Two things are specific to EC2:
- Open ports 80 and 443 in the instance's security group. Let's Encrypt checks your domain over port 80, so keep it open after you switch to HTTPS.
- Use an Elastic IP. A regular public IP changes when you stop and start the instance, and your DNS records would point to the wrong place.
The rest is the same as on any Ubuntu server. Follow our step-by-step guide on how to install Let's Encrypt for Nginx on Ubuntu.
Option 3: A paid certificate on Nginx
This part covers the full manual flow. You create the key and the certificate signing request (CSR) on the instance, the CA validates you and issues the certificate, and you install it in Nginx.
The commands are for Ubuntu 24.04. Connect to the instance over SSH as the default user (ubuntu on Ubuntu AMIs, ec2-user on Amazon Linux). That user can run sudo. Open ports 80 and 443 in the instance's security group first.
Step 1: Generate a private key and a CSR
Create a directory for the certificate files and generate a 2048-bit RSA key and a CSR in one command. List every name the certificate must cover in subjectAltName:
shell
1sudo mkdir -p /etc/ssl/example.com2cd /etc/ssl/example.com3sudo openssl req -new -newkey rsa:2048 -nodes \4 -keyout example.com.key -out example.com.csr \5 -subj "/CN=example.com" \6 -addext "subjectAltName=DNS:example.com,DNS:www.example.com"7sudo chmod 600 example.com.keyCheck the CSR before you send it to the CA:
shell
1openssl req -in example.com.csr -noout -verify -subject2openssl req -in example.com.csr -noout -text | grep -A1 "Subject Alternative Name"You should see Certificate request self-signature verify OK, subject=CN = example.com and both names in the list.
For a wildcard certificate, use -subj "/CN=*.example.com" and subjectAltName=DNS:*.example.com,DNS:example.com. A wildcard covers one level of subdomains, but not the bare domain, so list both. For OV and EV certificates, the CA collects your company details during the order, so you don't need them in the CSR.
The private key never leaves the server. Don't send it to the CA or paste it into an online CSR generator.
Step 2: Order the certificate and validate the domain
Buy the certificate from the CA or a reseller and paste the content of example.com.csr into the order form:
shell
1cat example.com.csrCopy everything, including the -----BEGIN CERTIFICATE REQUEST----- and -----END CERTIFICATE REQUEST----- lines.
Then prove you control the domain. CAs usually offer three methods: an email to an admin address, a DNS CNAME record, or a file on your website. For OV and EV, the CA also checks your organization, which can take a few days.
For the file method, the CA gives you a file name and its content. Create the file under /.well-known/pki-validation/ in the site's document root, here /var/www/example.com:
shell
1sudo mkdir -p /var/www/example.com/.well-known/pki-validation2sudo nano /var/www/example.com/.well-known/pki-validation/<FILE-NAME-FROM-CA>.txtCheck that the file opens over plain HTTP before you ask the CA to check it:
shell
1curl -i http://example.com/.well-known/pki-validation/<FILE-NAME-FROM-CA>.txtYou need HTTP/1.1 200 OK and the exact content from the CA.
Step 3: Build the full chain and check it
The CA sends you the server certificate and an intermediate bundle. Sectigo, for example, names them example_com.crt and example_com.ca-bundle. Copy both to the instance from your computer:
shell
1scp -i my-key.pem example_com.crt example_com.ca-bundle ubuntu@<instance-public-ip>:~Then, on the instance, combine them into fullchain.pem. The order matters: your server certificate goes first, the intermediates follow it. The root certificate isn't needed, because browsers already have it.
shell
1cd /etc/ssl/example.com2sudo cp ~/example_com.crt ~/example_com.ca-bundle /etc/ssl/example.com/3cat example_com.crt example_com.ca-bundle | sudo tee fullchain.pem > /dev/nullCheck the order. The first certificate must be issued to your domain, and each next one must be the issuer of the one before it:
shell
1openssl crl2pkcs7 -nocrl -certfile fullchain.pem | openssl pkcs7 -print_certs -nooutCheck that the chain leads to a trusted root:
shell
1openssl verify -untrusted example_com.ca-bundle example_com.crtThe answer should be example_com.crt: OK.
Finally, check that the certificate matches your private key. Both commands must print the same hash:
shell
1openssl x509 -in fullchain.pem -noout -pubkey | openssl sha2562sudo openssl pkey -in example.com.key -pubout | openssl sha256Step 4: Configure Nginx
Create /etc/nginx/sites-available/example.com, or replace the HTTP-only server block your site uses now:
nginx
1server {2 listen 80;3 listen [::]:80;4 server_name example.com www.example.com;5
6 location ^~ /.well-known/pki-validation/ {7 root /var/www/example.com;8 }9
10 location / {11 return 301 https://example.com$request_uri;12 }13}14
15server {16 listen 443 ssl;17 listen [::]:443 ssl;18 server_name example.com www.example.com;19
20 ssl_certificate /etc/ssl/example.com/fullchain.pem;21 ssl_certificate_key /etc/ssl/example.com/example.com.key;22 ssl_protocols TLSv1.2 TLSv1.3;23
24 root /var/www/example.com;25 index index.html;26}The first block sends all HTTP traffic to HTTPS, except the validation path, so the CA can check the domain again when you renew. The second block serves the site over HTTPS with your certificate and allows only TLS 1.2 and 1.3.
Keep that line. Ubuntu's default nginx.conf still lists the deprecated TLS 1.0 and 1.1. On Ubuntu 24.04, the system OpenSSL settings refuse them anyway, but the explicit line keeps the config safe on other systems and after someone loosens those settings.
This is a minimal working config. For one canonical address with or without www, modern cipher settings, HTTP/2 and HSTS, follow our guide on redirecting HTTP to HTTPS with Nginx.
Enable the site, test the config and reload Nginx:
shell
1sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/2sudo nginx -t3sudo systemctl reload nginxIf the site was already enabled, skip the first command. On Amazon Linux 2023, there's no sites-available directory: put the same config in /etc/nginx/conf.d/example.com.conf and run the last two commands.
Step 5: Check HTTPS with curl and openssl
Check the redirect and the HTTPS response:
shell
1curl -sI "http://example.com/blog/?page=2" | grep -iE "^(HTTP|location)"2curl -sI https://example.com/ | grep -iE "^HTTP"Expected output:
shell
1HTTP/1.1 301 Moved Permanently2Location: https://example.com/blog/?page=23HTTP/1.1 200 OKThen look at what the server actually sends with openssl s_client:
shell
1openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | grep -E "^ *[0-9] s:|^ *i:|Verify return code"A correct setup shows your certificate as 0, then the intermediates, each issued by the next one, and a successful check. For a Sectigo OV certificate, the top of the output looks like this:
shell
1 0 s:C = GB, ST = London, O = Example Ltd, CN = example.com2 i:C = GB, O = Sectigo Limited, CN = Sectigo Public Server Authentication CA OV R363 1 s:C = GB, O = Sectigo Limited, CN = Sectigo Public Server Authentication CA OV R364 i:C = GB, O = Sectigo Limited, CN = Sectigo Public Server Authentication Root R465Verify return code: 0 (ok)The names depend on your CA and certificate type, and the bundle may add one or two cross-signed certificates after these. That's normal. What matters is that 0 is your domain and the last line says Verify return code: 0 (ok).
Don't rely on the browser alone. Some browsers fetch a missing intermediate by themselves and show a padlock, while curl, mobile apps and API clients fail with the same certificate.
Renew a paid certificate
Renewing means getting a new certificate and installing it the same way. A renewed certificate doesn't extend the old file on your server.
Start about 30 days before expiry. Check the date of the certificate the server sends:
shell
1echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject -enddateGenerate a new key and CSR next to the current ones. A fresh key for each certificate is good practice, and if the old key ever leaked, the new certificate isn't affected:
shell
1cd /etc/ssl/example.com2sudo openssl req -new -newkey rsa:2048 -nodes \3 -keyout example.com.new.key -out example.com.new.csr \4 -subj "/CN=example.com" \5 -addext "subjectAltName=DNS:example.com,DNS:www.example.com"6sudo chmod 600 example.com.new.keySend example.com.new.csr to the CA as a renewal or reissue, and pass the domain check again. Since the validation path stays open on port 80, the file method works with HTTPS turned on.
When the new files arrive, copy them to the instance like in step 3, build the new chain and check that it matches the new key:
shell
1sudo cp ~/example_com.crt ~/example_com.ca-bundle /etc/ssl/example.com/2cat example_com.crt example_com.ca-bundle | sudo tee fullchain.new.pem > /dev/null3openssl x509 -in fullchain.new.pem -noout -pubkey | openssl sha2564sudo openssl pkey -in example.com.new.key -pubout | openssl sha256Keep a copy of the old pair, swap in the new one, and reload Nginx:
shell
1sudo cp fullchain.pem fullchain.old.pem && sudo cp example.com.key example.com.old.key2sudo mv fullchain.new.pem fullchain.pem && sudo mv example.com.new.key example.com.key3sudo nginx -t && sudo systemctl reload nginxRun the s_client command from the start of this section again. You should see the new expiry date.
To avoid surprises, check expiry from a cron job or your monitoring. This command exits with code 1 if the certificate expires within 30 days:
shell
1openssl x509 -checkend $((30*86400)) -noout -in /etc/ssl/example.com/fullchain.pemDoing this by hand every few months gets old fast.
Many commercial CAs, Sectigo and DigiCert among them, support ACME for paid certificates. Your CA gives you an ACME server address and External Account Binding (EAB) keys, and Certbot takes over the renewals like it does for Let's Encrypt. Ask your CA or reseller if your plan includes it.
Common errors
invalid parameter "SSL". Nginx parameters are case-sensitive. Writelisten 443 ssl;in lowercase.unknown directive "ssl". The oldssl on;line was removed in Nginx 1.25.1. Delete it, sincelisten 443 ssl;already does the job. Older versions like 1.24 on Ubuntu 24.04 still accept it, with a deprecation warning.SSL_CTX_use_PrivateKey(...) failed ... key values mismatch. Either the certificate and key don't belong together, orfullchain.pemstarts with the intermediate instead of your certificate. Run the two hash commands from step 3 and check the order.- curl says
unable to get local issuer certificate, the browser shows a padlock. The intermediate is missing fromfullchain.pem.s_clientshows only certificate0andVerify return code: 21 (unable to verify the first certificate). BIO_new_file("/etc/ssl/dhparam.pem") failed. The config points to a DH parameters file that doesn't exist. Removessl_dhparam: modern ECDHE ciphers don't need it.- The site works over HTTP but HTTPS times out. Port 443 isn't open in the security group, or on an ALB setup, the instance's security group doesn't allow the load balancer.
Wrapping up
Pick the option that matches how traffic reaches your instance. ACM on a load balancer is free and renews itself, Let's Encrypt covers a single instance for free, and a paid OV or EV certificate is for when someone asks for it.
With certificates now valid for 200 days and soon 100, whatever you choose has to renew on its own or be watched by monitoring.
If you'd rather not keep track of certificates, load balancers and the rest of your AWS setup yourself, our team can do it for you. Take a look at our cloud engineering services.
