Skip to content

Instantly share code, notes, and snippets.

@crpb
Last active January 17, 2026 07:40
Show Gist options
  • Select an option

  • Save crpb/6e25e7e99651d88bdbb8ac1ae38b2051 to your computer and use it in GitHub Desktop.

Select an option

Save crpb/6e25e7e99651d88bdbb8ac1ae38b2051 to your computer and use it in GitHub Desktop.
certbot-hetzner

This is basically a rewrite of it so the instructions still apply.

Make sure to protect your api-tokens on a shared system!

chmod -v 0600 /etc/hetzner-dns-token
# alt. with multiple keys
mkdir -v -m 0700 /etc/hetzner-dns-tokens
# after placing them in there as `/etc/hetzner-dns-tokens/$some.tld`
chmod -v 0600 /etc/hetzner-dns-tokens/*

Depends

  • sh
  • curl
  • jq
  • make (optional)
  • install (optional)

Installation

This will place the two hook-scripts and the wrapper certbot-hetzner into /usr/local/sbin.

make install

Deinstallation

You guessed it!

make uninstall

If you don't use it anymore you might also want to remove any tokens you stored in /etc/.

Usage

To request a new certificate.

The script will run a --dry-run staging check and if everything checks out it will request the real certificate.

certbot-hetzner -d some.tld -d *.some.tld

TODO

Rewrite to one script with individual call via basename.

#!/bin/sh
# SPDX-License-Identifier: MIT
# revamped https://community.hetzner.com/tutorials/letsencrypt-dns
set -e
#set -x
[ -z "${CERTBOT_DOMAIN}" ] && exit 66
[ -z "${CERTBOT_VALIDATION}" ] && exit 66
TOKEN=/etc/hetzner-dns-token
TOKEN_DIR=/etc/hetzner-dns-tokens
CERTBOT_TTL="${TTL:-300}"
DOMAIN_NAME=$(expr "$CERTBOT_DOMAIN" : '.*\.\(.*\..*\)')
SUBDOMAIN=".${CERTBOT_DOMAIN%."$DOMAIN_NAME"}"
[ "$CERTBOT_DOMAIN" = "$DOMAIN_NAME" ] && SUBDOMAIN=
# ONE TOKEN/DOMAIN? *untested*
if [ -d "$TOKEN_DIR" ]; then
DOMAIN_TOKEN="$TOKEN_DIR"/"$CERTBOT_DOMAIN"
if [ -f "$DOMAIN_TOKEN" ]; then
TOKEN="$DOMAIN_TOKEN"
fi
fi
[ -r "$TOKEN" ] || exit 77
API_TOKEN="$(cat "$TOKEN")"
# # https://docs.hetzner.cloud/reference/cloud#zone-rrsets-create-an-rrset
# DATA=$(jq --null-input \
# --arg certbot_ttl "$CERTBOT_TTL" \
# --arg certbot_value "${CERTBOT_VALIDATION}" \
# --arg certbot_name "_acme-challenge.${CERTBOT_DOMAIN}." \
# '
# {name: $certbot_name,
# type: "TXT",
# ttl: $certbot_ttl | tonumber,
# records: [{
# value: $certbot_value | tojson
# }]
# }')
# API_URL=zones/"$DOMAIN_NAME"/rrsets
# https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-add-records-to-an-rrset
# For convenience, the RRSet will be automatically created if it doesn't exist.
# Otherwise, the new records are appended to the existing RRSet.
DATA=$(jq --null-input \
--arg certbot_ttl "$CERTBOT_TTL" \
--arg certbot_value "${CERTBOT_VALIDATION}" \
'
{ ttl: $certbot_ttl | tonumber,
records: [{
value: $certbot_value | tojson
}]
}')
API_URL=zones/"$DOMAIN_NAME"/rrsets/_acme-challenge"$SUBDOMAIN"/TXT/actions/add_records
curl -sSfL -o /dev/null \
-X POST \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Content-Type: application/json" \
-d "$DATA" \
https://api.hetzner.cloud/v1/"$API_URL"
RET="$?"
[ "$RET" -eq 0 ] && sleep 20
return "$RET"
#!/bin/sh
# SPDX-License-Identifier: MIT
# revamped https://community.hetzner.com/tutorials/letsencrypt-dns
set -e
#set -x
[ -z "${CERTBOT_DOMAIN}" ] && exit 66
[ -z "${CERTBOT_VALIDATION}" ] && exit 66
TOKEN=/etc/hetzner-dns-token
TOKEN_DIR=/etc/hetzner-dns-tokens
DOMAIN_NAME=$(expr "$CERTBOT_DOMAIN" : '.*\.\(.*\..*\)')
SUBDOMAIN=".${CERTBOT_DOMAIN%."$DOMAIN_NAME"}"
[ "$CERTBOT_DOMAIN" = "$DOMAIN_NAME" ] && SUBDOMAIN=
# ONE TOKEN/DOMAIN? *untested*
if [ -d "$TOKEN_DIR" ]; then
DOMAIN_TOKEN="$TOKEN_DIR"/"$CERTBOT_DOMAIN"
if [ -f "$DOMAIN_TOKEN" ]; then
TOKEN="$DOMAIN_TOKEN"
fi
fi
[ -r "$TOKEN" ] || exit 77
API_TOKEN="$(cat "$TOKEN")"
API_URL=zones/"$DOMAIN_NAME"/rrsets/_acme-challenge"$SUBDOMAIN"/TXT
curl -sSfL -o /dev/null \
-X DELETE \
-H "Authorization: Bearer ${API_TOKEN}" \
https://api.hetzner.cloud/v1/"$API_URL"
return "$?"
#!/bin/sh
# SPDX-License-Identifier: MIT
# shellcheck disable=SC2086,SC2068
BINDIR=/usr/local/sbin
if [ "$1" = "-h" ]; then
cat << 'EOF'
Usage: certbot-hetzner -d some.tld [-d sub.some.tld ..]
EOF
return 0
fi
# we at least need '-d some.tld'
[ "$#" -ge 2 ] || return 1
OPTS="certonly --manual --preferred-challenges=dns \
--manual-auth-hook ${BINDIR}/certbot-hetzner-auth.sh \
--manual-cleanup-hook ${BINDIR}/certbot-hetzner-cleanup.sh"
printf '\033[1mStarting staging test:\033[0m\n'
if certbot $OPTS --dry-run $@; then
printf '\n\033[1mRequesting Certificate:\033[0m\n'
certbot $OPTS $@
fi
Copyright (c) 2020-2024 Hetzner Online GmbH
Copyright (c) 2026 Christopher Bock <christopher@bocki.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
“Software”), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
BINDIR=/usr/local/sbin
OWNER=root
GROUP=root
install:
@echo installing certbot-hetzner scripts
install -v -o $(OWNER) -g $(GROUP) -m 0775 certbot-hetzner-auth.sh $(BINDIR)/
install -v -o $(OWNER) -g $(GROUP) -m 0775 certbot-hetzner-cleanup.sh $(BINDIR)/
install -v -o $(OWNER) -g $(GROUP) -m 0775 certbot-hetzner.sh $(BINDIR)/certbot-hetzner
uninstall:
@echo removing certbot-hetzner scripts
rm -vf $(BINDIR)/certbot-hetzner-auth.sh
rm -vf $(BINDIR)/certbot-hetzner-cleanup.sh
rm -vf $(BINDIR)/certbot-hetzner
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment