macOS SSL certificate with Lego and LaunchDaemon
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.
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.
Frequently asked questions
Find answers to the most common questions about SSL certificates and FairSSL.
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.uname -m) and downloads the correct Lego binary. Lego supports both amd64 (Intel) and arm64 (Apple Silicon).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.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./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 issue your first certificate in under 10 minutes.