ACME client Lego
A detailed step-by-step guide to installing, configuring, and automatically renewing an SSL certificate on a Linux server using the Lego ACME client.
The ACME client Lego is an independent, free, open-source project written in the Go language. It is ideal for custom integration and scripting and enjoys wide support from domain registrars and DNS providers. Lego is a flexible ACME client that can be easily integrated into custom systems and scripts. Besides HTTP-01 validation, it offers DNS validation through many DNS providers (list of supported DNS providers) for obtaining WildCard SSL certificates.
The guide uses syntax verified on version Lego 5.*.* and is intended for Debian/Ubuntu with Apache 2 and the ACME client Lego.
Article contents
- Lego installation
- Apache, webroot
- Lego configuration files
- Certificate issuance
- Deployment to Apache
- Automatic renewal
Basic concepts
- ACME – protocol for automated issuance and renewal of SSL/TLS certificates.
- HTTP-01 – ACME validation method that verifies domain ownership using a temporary file accessible over HTTP.
- DNS-01 – validation method via the DNS TXT record
_acme-challenge. - EAB kid + hmac – External Account Binding (EAB) details from the certificate authority. They link ACME client to an account or product.
- Systemd service - a configuration file that tells the Linux system how to start an application and keep it running even after a server restart.
If the domain example.com appears in the examples, always replace it with your own domain.
Lego installation
apt update
apt install -y curl tar
cd /tmp
LEGO_URL=$(curl -s https://api.github.com/repos/go-acme/lego/releases/latest | sed -n 's/.*"browser_download_url": "\(.*linux_amd64.tar.gz\)".*/\1/p' | head -n1)
echo "$LEGO_URL"
curl -L -o lego.tar.gz "$LEGO_URL"
tar -xzf lego.tar.gz
install -m 0755 lego /usr/local/bin/lego
lego --version
After a successful installation, we recommend removing the temporary files.
rm -f /tmp/lego /tmp/lego.tar.gz /tmp/LICENSE /tmp/CHANGELOG.md
| Command / value | What it does / what to replace |
|---|---|
apt update |
Updates the package list. |
apt install -y curl tar |
Installs the tools for downloading and extracting Lego. |
LEGO_URL=... |
Finds the URL of the latest Linux amd64 release package. |
curl -L -o lego.tar.gz |
Downloads the Lego archive. |
tar -xzf lego.tar.gz |
Extracts the archive. |
install -m 0755 lego /usr/local/bin/lego |
Installs Lego as an executable system command. |
lego --version |
Verifies the installed version of Lego. |
Apache, webroot
This procedure creates a basic VirtualHost configuration for the domain on port 80. It sets DocumentRoot, permissions for the web directory, creates Apache logs, enables the configuration using a2ensite, verifies its correctness (apache2ctl configtest) and reloads the changes. Finally, it verifies the website's availability using an HTTP request curl.
Before running, replace the value example.com in the line DOMAIN="example.com" with your own domain. The variable $DOMAIN is then used in the following commands for paths, the Apache vhost and the test page.
cd /var/www
apt update
apt install -y apache2
systemctl enable --now apache2
a2enmod rewrite headers ssl
systemctl reload apache2
# or just updates
apt update
apt install --only-upgrade apache2
systemctl reload apache2
DOMAIN="example.com"
mkdir -p /var/www/$DOMAIN/public
chown -R www-data:www-data /var/www/$DOMAIN
chmod -R 755 /var/www/$DOMAIN
echo "OK $DOMAIN" > /var/www/$DOMAIN/public/index.html
| Command / value | What it does / what to replace |
|---|---|
cd /var/www |
Changes to the directory where web files are usually stored. |
apt update |
Updates the package list. |
apt install -y apache2 |
Installs Apache; -y automatically confirms the installation. |
systemctl enable --now apache2 |
Enables Apache at server startup and starts it at the same time. |
a2enmod rewrite headers ssl |
Enables modules for redirects, headers and HTTPS. |
DOMAIN="example.com" |
Sets the domain variable. Replace example.com with your own domain. |
mkdir/chown/chmod/echo |
Creates the webroot, sets permissions for Apache and saves a simple test page. |
HTTP vhost for both the apex and subdomain:
cat > /etc/apache2/sites-available/$DOMAIN.conf <<EOF
<VirtualHost *:80>
ServerName $DOMAIN
ServerAlias www.$DOMAIN
DocumentRoot /var/www/$DOMAIN/public
<Directory /var/www/$DOMAIN/public>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog \${APACHE_LOG_DIR}/${DOMAIN}_error.log
CustomLog \${APACHE_LOG_DIR}/${DOMAIN}_access.log combined
</VirtualHost>
EOF
a2ensite "$DOMAIN.conf"
apache2ctl configtest
systemctl reload apache2
curl -I "http://$DOMAIN"
curl -I "http://www.$DOMAIN"
Result: After opening http://example.com, the test page should appear.
| Command / value | What it does / what to replace |
|---|---|
cat > ... <<EOF |
Writes a new Apache HTTP vhost to a file in sites-available. |
ServerName $DOMAIN |
The main domain of the virtual host. |
ServerAlias www.$DOMAIN |
Creates handling for the first-level subdomain. |
DocumentRoot |
The directory from which Apache serves content. |
a2ensite "$DOMAIN.conf" |
Enables the vhost. |
apache2ctl configtest |
Verifies the Apache configuration syntax. |
curl -I http://$DOMAIN |
Verifies the domain's HTTP response. |
Lego configuration files
The recommended approach for Lego v5 is to store the settings in a configuration file. The systemd service then does not need to contain a long command with domains and hooks.
Configuration file lego.yml
The .yml file is a text configuration file in YAML format, used for a clear notation of settings, parameters and structured data. Before saving the YAML configuration, replace example.com with your own domain, your@email.com with your contact e-mail and the KID / HMAC values with the details from your ACME certificate order.
mkdir /etc/lego/$DOMAIN
nano /etc/lego/$DOMAIN/lego.yml
storage: /etc/lego/example.com
accounts:
certum-account:
server: certum
email: your@email.com # your email address for CA Certum
acceptsTermsOfService: true
eab:
kid: KID
hmacKey: HMAC
servers:
certum:
url: https://acme.certum.pl/directory
challenges:
http-chal:
http:
# Path to your website's document root.
# Lego will temporarily write a file to this directory .well-known/acme-challenge/
webroot: /var/www/example.com/public
certificates:
example-com:
account: certum-account
challenge: http-chal
domains:
- example.com
- www.example.com
renew:
days: 30
hooks:
deploy:
command: systemctl reload apache2
Tip! You can generate an almost complete YML content directly on the server and then just fill in the correct e-mail, kid and hmacKey. Just run the command below and copy the content from the index.html page into the lego.yml file.
›› Show/Hide the prepared YML.
cat > "/var/www/$DOMAIN/public/index.html" <<EOF
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>${DOMAIN}</title>
<style>
body { font-family: sans-serif; max-width: 900px; margin: 40px auto; }
pre { background:#f4f4f4; padding:1em; overflow:auto; }
</style>
</head>
<body>
<h1>OK – ${DOMAIN}</h1>
<p>Apache is working correctly.</p>
<h2>lego.yml</h2>
<pre><code>storage: /etc/lego/${DOMAIN}
accounts:
certum-account:
server: certum
email: YOUR_EMAIL
acceptsTermsOfService: true
eab:
kid: YOUR_KID
hmacKey: YOUR_HMAC_KEY
servers:
certum:
url: https://acme.certum.pl/directory
challenges:
http-chal:
http:
webroot: /var/www/${DOMAIN}/public
certificates:
${DOMAIN//./-}:
account: certum-account
challenge: http-chal
domains:
- ${DOMAIN}
- www.${DOMAIN}
renew:
days: 30
hooks:
deploy:
command: systemctl reload apache2
</code></pre>
</body>
</html>
EOF
The lego.yml file contains the EAB HMAC, so it must have restricted permissions. In documentation, use only placeholders.
chmod 600 /etc/lego/$DOMAIN/lego.yml
Checking the file's permissions and owner:
stat -c "%a %U:%G %n" /etc/lego/$DOMAIN/lego.yml
| Command / value | What it does / what to replace |
|---|---|
storage |
Directory for the Lego account, certificates and metadata. |
accounts |
Definition of the ACME account including the e-mail and EAB details. |
servers.certum.url |
The Certum ACME endpoint. |
challenges.http-chal |
Validation over http. |
certificates |
List of certificates that Lego should manage. |
domains |
The apex domain and the wildcard domain in the certificate. |
renew.days |
How many days before expiration Lego should renew. |
hooks.deploy.command |
Command after a successful issuance or renewal, here reloading Apache. |
Issuance of the SSL/TLS certificate
Before running, check echo ${DOMAIN} or set the DOMAIN variable to the name of your domain DOMAIN="example.com". The Lego tool performs HTTP-01 validation using a file temporarily stored in the webroot, verifies domain ownership and then creates an SSL/TLS certificate. The certificate, private key and issuer (intermediate) certificate will be stored in the directory /etc/lego/${DOMAIN}/certificates/.
lego --config /etc/lego/$DOMAIN/lego.yml
During generation, the ACME client Lego will print information about the request:
root@vmiXXXXXXXX:~# echo ${DOMAIN}
example.com
root@:~# lego --config /etc/lego/$DOMAIN/lego.yml
INFO Archive account scope=accountID filepath=/etc/lego/example.com/accounts/acme.certum.pl/certum-acme/
archives=/etc/lego/example.com/archives/accounts/acme.certum.pl_certum-acme_1785270773.zip
INFO Private key saved. filepath=/etc/lego/example.com/accounts/acme.certum.pl/certum-account/certum-account.key
INFO Registering the account (EAB). email=your@email.com
WARN !!!! HEADS UP !!!!
Your account credentials have been saved in your
configuration directory at "/etc/lego/example.com/accounts".
You should make a secure backup of this folder now. This
configuration directory will also contain private keys
generated by lego and certificates obtained from the ACME
server. Making regular backups of this folder is ideal.
INFO Obtaining bundled SAN certificate. domains="example.com, www.example.com"
INFO Use solver. domain=www.example.com type=http-01
INFO Use solver. domain=example.com type=http-01
INFO http01: Trying to solve HTTP-01. domain=www.example.com
INFO The server validated our request. domain=www.example.com
INFO http01: Trying to solve HTTP-01. domain=example.com
INFO The server validated our request. domain=example.com
INFO Validations succeeded; requesting certificates. domains="example.com, www.example.com"
INFO Waiting for certificates. timeout=30s interval=500ms domains="example.com, www.example.com"
INFO Server responded with a certificate. domains="example.com, www.example.com"
INFO Writing file. filepath=/etc/lego/example.com/certificates/acmeapi-online.crt
INFO Writing file. filepath=/etc/lego/example.com/certificates/acmeapi-online.issuer.crt
INFO Writing file. filepath=/etc/lego/example.com/certificates/acmeapi-online.key
INFO Writing file. filepath=/etc/lego/example.com/certificates/acmeapi-online.pem
INFO Writing file. filepath=/etc/lego/example.com/certificates/acmeapi-online.json
Verify the generated SSL certificate files
Shows the contents of the certificates directory created by the Lego service, including the certificate, private key and issuer certificate for the selected domain.
ls -la /etc/lego/$DOMAIN/certificates/
The certificates/ directory contains the issued .crt, .key, intermediate certificates of the certificate authority and metadata.
Deploying the certificate to Apache
This example uses the variable ${DOMAIN}, which you should already have set from the beginning of the guide. Before running the commands, you can make sure the variable is set correctly, for example: echo ${DOMAIN}
The variable ${DOMAIN} is used in the configuration file name, the ServerName and ServerAlias values and the path to the webroot.
Warning! - the paths to the SSL certificate and private key use the domain in the form example-com. The paths must match the domain used in the Lego configuration.
cat > /etc/apache2/sites-available/${DOMAIN}-le-ssl.conf <<EOF
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName ${DOMAIN}
ServerAlias www.${DOMAIN}
DocumentRoot /var/www/${DOMAIN}/public
<Directory /var/www/${DOMAIN}/public>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
SSLEngine on
SSLCertificateFile /etc/lego/${DOMAIN}/certificates/${DOMAIN//./-}.crt
SSLCertificateKeyFile /etc/lego/${DOMAIN}/certificates/${DOMAIN//./-}.key
ErrorLog ${APACHE_LOG_DIR}/${DOMAIN}_ssl_error.log
CustomLog ${APACHE_LOG_DIR}/${DOMAIN}_ssl_access.log combined
</VirtualHost>
</IfModule>
EOF
a2ensite ${DOMAIN}-le-ssl.conf
apache2ctl configtest
systemctl reload apache2
curl -I https://${DOMAIN}
curl -I https://www.${DOMAIN}
Result: Working HTTPS.
| Command / value | What it does / what to replace |
|---|---|
cat > ...-le-ssl.conf |
Creates the Apache HTTPS vhost. |
ServerName / ServerAlias |
Specifies the apex domain and the subdomain. |
SSLCertificateFile |
Path to the certificate. |
SSLCertificateKeyFile |
Path to the private key. |
a2ensite |
Enables the HTTPS vhost. |
systemctl reload apache2 |
Reloads the new Apache configuration. |
curl -I https://... |
Verifies the HTTPS response. |
Automatic renewal
Lego can renew the certificate automatically, but after installation it does not create the systemd units for regular execution on its own. Two units therefore need to be created for automatic renewal:
- lego-example-com-renew.service – runs the check and, if needed, the renewal of the certificate.
- lego-example-com-renew.timer – ensures the service runs daily at a set time.
Before inserting, replace example-com in the service/timer name with your own name if needed, and replace example.com in the configuration path with your own domain.
cat > /etc/systemd/system/lego-${DOMAIN//./-}-renew.service <<EOF
[Unit]
Description=Renew ACME Certum SSL for example.com using Lego HTTP-01
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/lego --config /etc/lego/${DOMAIN}/lego.yml
EOF
cat > /etc/systemd/system/lego-${DOMAIN//./-}-renew.timer <<EOF
[Unit]
Description=Daily Lego renewal check for ${DOMAIN}
[Timer]
OnCalendar=*-*-* 03:20:00
RandomizedDelaySec=1800
Persistent=true
[Install]
WantedBy=timers.target
EOF
After creating the units, verify their contents:
cat /etc/systemd/system/lego-example-com-renew.service
echo "----------------"
cat /etc/systemd/system/lego-example-com-renew.timer
Reload the new units, enable the timer and verify it is running:
systemctl daemon-reload
systemctl enable --now lego-${DOMAIN//./-}-renew.timer
systemctl list-timers | grep lego
Result: The timer is active and systemd has scheduled its next run.
| Command / value | What it does / what to replace |
|---|---|
lego-example-com-renew.service |
Systemd service for a one-time run of Lego renew/run. |
Type=oneshot |
The service starts, does its work and finishes. |
ExecStart |
Runs Lego according to lego.yml. |
lego-example-com-renew.timer |
Systemd timer that runs the service regularly. |
OnCalendar |
Time of the daily check. |
RandomizedDelaySec |
Random delay so that the requests do not all start at exactly the same time. |
Persistent=true |
Runs a missed execution after the server starts. |
systemctl enable --now |
Enables the timer and activates it immediately. |
Safe test of the service:
systemctl start lego-${DOMAIN//./-}-renew.service
systemctl status lego-${DOMAIN//./-}-renew.service --no-pager
journalctl -u lego-${DOMAIN//./-}-renew.service -n 100 --no-pager
Result: If the certificate is not close to expiration, Lego may report that renewal is not needed. This is correct behavior.
| Command / value | What it does / what to replace |
|---|---|
systemctl start ...service |
Manually runs the renewal service for a test. |
systemctl status ... |
Shows whether the service finished successfully |
journalctl -u ... |
Shows the service's latest logs. |
List of available Lego units:
ls -l /etc/systemd/system/lego*
systemctl list-timers | grep lego
Result: Both variants show all services and timers related to the Lego ACME client.
Where to next?
Back to Help
Found an error or don't understand something? Write us!
