SSL certificates can be valid for at most 199 days. By 15 March 2027 the limit becomes 99 days, for DigiCert certificates from 24 February 2027. Read more →

macOS SSL certificate with Lego and LaunchDaemon

Lego runs on the Mac, fetches the certificate from FairSSL, and a sync script puts it in place for the service and restarts it.

How it works

  1. Install Lego The install command fetches Lego for Intel or Apple Silicon and places it in /usr/local/bin/lego.
  2. Create the sync script fairssl-lego-sync.sh runs as a deploy hook. Set the path and restart command for the service, for example nginx or Apache.
  3. Issue and schedule Run lego run against https://fairssl.dk/acme with the EAB keys and FairSSL AutoDNS, and let a LaunchDaemon run Lego daily. ARI drives renewal.
  4. Check Test the LaunchDaemon with launchctl, read /var/log/lego.log and lego.err, and test with FairSSL's SSL scanner.

← All guides

macOS is rarely a public web server, but when it is, Lego plus a LaunchDaemon works beautifully. The whole setup is one Go binary plus two scripts, and it works on both Intel and Apple Silicon. We use Lego v5.0.4 or later for ARI support.

Supported versions

macOSVersionsThe guide sets no version limitRuns onmacOSACME clientlego 5.0.4+

Setup

Step 1: Install Lego

The following command fetches the latest Lego release, detects the architecture and installs the binary to /usr/local/bin/lego. It also creates the Lego storage folders.

sudo mkdir -p /usr/local/bin
arch=$(uname -m | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/')
version=$(curl -fsSL https://api.github.com/repos/go-acme/lego/releases/latest \
  | sed -n 's/.*"tag_name": "\(v[^"]*\)".*/\1/p' | head -1)
curl -fL "https://github.com/go-acme/lego/releases/download/${version}/lego_${version}_darwin_${arch}.tar.gz" -o lego.tar.gz
tar -xzf lego.tar.gz lego
sudo install -m 0755 lego /usr/local/bin/lego
rm -f lego lego.tar.gz
sudo install -d -m 0755 /usr/local/etc/lego
sudo install -d -m 0700 /usr/local/etc/lego/accounts /usr/local/etc/lego/certificates

Step 2: Create the sync script

Lego runs this script via --deploy-hook after every successful issuance or renewal. Adjust the 4 variables at the top to match your environment. The restart command can be sudo nginx -s reload, sudo apachectl -k graceful, or something more specific to whatever service you run.

sudo tee /usr/local/bin/fairssl-lego-sync.sh > /dev/null << 'SCRIPT'
#!/bin/bash
set -euo pipefail
TARGET="/usr/local/etc/ssl"
CERT_FILE="cert.pem"
KEY_FILE="key.pem"
RESTART_CMD="sudo nginx -s reload"

lego_name="$${1:-}"
cert_path="$${LEGO_HOOK_CERT_PATH:-}"
key_path="$${LEGO_HOOK_CERT_KEY_PATH:-}"
if { [ -z "$cert_path" ] || [ -z "$key_path" ]; } && [ -n "$lego_name" ]; then
    cert_path="/usr/local/etc/lego/certificates/${lego_name}.crt"
    key_path="/usr/local/etc/lego/certificates/${lego_name}.key"
fi
if [ -z "$cert_path" ] || [ -z "$key_path" ] || [ ! -s "$cert_path" ] || [ ! -s "$key_path" ]; then
    echo "Missing Lego certificate files. Upgrade to Lego v5.0.4+ and check /usr/local/etc/lego/certificates."
    exit 1
fi
sudo install -d -m 0755 "$TARGET"
sudo install -m 0644 "$cert_path" "$TARGET/$CERT_FILE"
sudo install -m 0600 "$key_path" "$TARGET/$KEY_FILE"
$RESTART_CMD
SCRIPT
sudo chmod +x /usr/local/bin/fairssl-lego-sync.sh

Step 3: Issue the certificate

Make sure the domain is configured for FairSSL AutoDNS. Grab the EAB keys from the ACME tab in your FairSSL control panel and run Lego.

sudo /usr/local/bin/lego run \
  --domains "www.example.com" \
  --server https://fairssl.dk/acme \
  --eab --eab.kid YOUR_EAB_KID --eab.hmac YOUR_EAB_HMAC \
  --accept-tos --email acme-client@fairssl.dk \
  --http --http.webroot "/Library/WebServer/Documents" \
  --path "/usr/local/etc/lego" \
  --deploy-hook "/usr/local/bin/fairssl-lego-sync.sh www-example-com"

Step 4: Schedule renewal with LaunchDaemon

macOS uses LaunchDaemons rather than cron. Drop in this plist and Lego will run daily at a randomised time. ARI decides when an actual renewal happens; --renew-days 7 is the fallback.

sudo tee /Library/LaunchDaemons/com.fairssl.lego-www-example-com.plist > /dev/null << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.fairssl.lego-www-example-com</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/bash</string>
        <string>-c</string>
        <string>/usr/local/bin/lego run --domains "www.example.com" --server https://fairssl.dk/acme --accept-tos --email acme-client@fairssl.dk --http --http.webroot "/Library/WebServer/Documents" --path "/usr/local/etc/lego" --renew-days 7 --deploy-hook "/usr/local/bin/fairssl-lego-sync.sh www-example-com"</string>
    </array>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>4</integer>
        <key>Minute</key>
        <integer>17</integer>
    </dict>
    <key>StandardOutPath</key>
    <string>/var/log/lego.log</string>
    <key>StandardErrorPath</key>
    <string>/var/log/lego.err</string>
    <key>RunAtLoad</key>
    <false/>
</dict>
</plist>
EOF
sudo chown root:wheel /Library/LaunchDaemons/com.fairssl.lego-www-example-com.plist
sudo chmod 644 /Library/LaunchDaemons/com.fairssl.lego-www-example-com.plist
sudo launchctl load -w /Library/LaunchDaemons/com.fairssl.lego-www-example-com.plist

Step 5: Verify and debug

  • Test the LaunchDaemon manually: sudo launchctl start com.fairssl.lego-www-example-com
  • Check logs: tail -f /var/log/lego.log /var/log/lego.err
  • Verify the certificate with FairSSL's SSL scanner.
  • To debug the ACME flow itself, prefix the command with LEGO_DEBUG_ACME_HTTP_CLIENT=true.

Certificates on many servers and appliances

A publicly trusted SSL certificate can be valid for at most 199 days now, 99 days by 15 March 2027 and 46 days by 15 March 2029. Each certificate then has to be replaced at least twice a year now and at least eight times a year from 2029, on every server, firewall and load balancer that uses it. sslbrain from FairSSL runs as an appliance in your own network and renews and installs the certificates from one place, including on devices without an agent.

sslbrain compared with Certbot, win-acme and lego(opens sslbrain.com in a new tab)

Frequently asked questions

Find answers to the most common questions about SSL certificates and FairSSL.

Lego is a single Go binary with no dependencies, which suits macOS installs where you do not want Python or Homebrew managing the certificate client. Lego supports ARI (RFC 9773), EAB and a broad list of DNS providers. Certbot also works, but pulls in Python and a wider dependency stack.
Yes. macOS has moved away from cron. launchd via a LaunchDaemon is the recommended way to schedule background jobs that need to run regardless of user session. Our example installs a LaunchDaemon in /Library/LaunchDaemons that runs Lego daily at a randomised time.
Yes. The install script auto-detects the architecture (uname -m) and downloads the correct Lego binary. Lego supports both amd64 (Intel) and arm64 (Apple Silicon).
A separate sync script (fairssl-lego-sync.sh) runs via Lego's --deploy-hook after every successful issuance or renewal. Adjust the script with the path your service expects and a restart command. The script ships example restart commands for nginx, Apache and generic services.
From Lego v5 the renew command is gone. lego run checks ARI and only renews if needed. You can force renewal with --renew-days 7, which our example uses as a fallback if ARI does not respond.
Only for the first run. Lego stores the ACME account locally under /usr/local/etc/lego/accounts, and future renewals reuse it without EAB. Keep the keys somewhere safe as a backup in case you ever need to rebuild the setup.

Ready to automate macOS certificates?

Create a free account and order your first certificate. A DV certificate is issued in under 2 minutes.