How to add Let’s Encrypt SSL certificate to Proxmox

Usually Proxmox instances does not listen on 80/443 port or Proxmox instance is not exposed to internet.
In this way we can’t use certbot software for provisioning certificates to our instances.
The most comfortable way for getting certificates in this case is using Dehydrated and Lexicon both.

Note: In this post I use Yandex.Mail for a domain as a free DNS hosting, you can use which one do you want (based on providers supported for lexicon)

Install Lexicon

Lexicon provide a way to manipulate DNS records and supported a lot of providers.

apt-get update
apt-get install -y build-essential python-dev curl libffi-dev libssl-dev python-pip
pip install requests[security]
pip install dns-lexicon

Install Dehydrated

The first step is install dehydrated dependencies:

apt-get install -y openssl curl sed grep mktemp git

After them – install dehydrated to opt
git clone https://github.com/lukas2511/dehydrated.git /opt/dehydrated

Configure it!

Create directory for dehydrated config:

mkdir -p /etc/dehydrated

And paste his config to /etc/dehydrated/config
CHALLENGETYPE="dns-01"
BASEDIR=$SCRIPTDIR
DOMAINS_TXT="${BASEDIR}/config/domains.txt"
CERTDIR="${BASEDIR}/certs"
HOOK="${BASEDIR}/config/hook.sh"
CONTACT_EMAIL=replayceme@exaple.com

Create directory for hooks and domains list:
mkdir -p /opt/dehydrated/config

Paste your domain to list:
echo "mydomain.example.com" > /opt/dehydrated/config/domains.txt

Create hook:
vi /opt/dehydrated/config/hook.sh

Hook examples:

For Proxmox 4.X

#!/usr/bin/env bash
set -e
set -u
set -o pipefail
export PROVIDER=${PROVIDER:-"yandex"}
export LEXICON_YANDEX_TOKEN=PLACE_TOKEN_HERE
deploy_challenge() {
local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
echo "deploy_challenge called: ${DOMAIN}, ${TOKEN_FILENAME}, ${TOKEN_VALUE}"
lexicon $PROVIDER create ${DOMAIN} TXT --name="_acme-challenge.${DOMAIN}." --content="${TOKEN_VALUE}"
sleep 30
}
clean_challenge() {
local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
echo "clean_challenge called: ${DOMAIN}, ${TOKEN_FILENAME}, ${TOKEN_VALUE}"
lexicon $PROVIDER delete ${DOMAIN} TXT --name="_acme-challenge.${DOMAIN}." --content="${TOKEN_VALUE}"
}
deploy_cert() {
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}"
echo "deploy_cert called: ${DOMAIN}, ${KEYFILE}, ${CERTFILE}, ${FULLCHAINFILE}, ${CHAINFILE}"
cp ${FULLCHAINFILE} /etc/pve/local/pveproxy-ssl.pem
cp ${KEYFILE} /etc/pve/local/pveproxy-ssl.key
systemctl restart pveproxy
}
unchanged_cert() {
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}"
echo "unchanged_cert called: ${DOMAIN}, ${KEYFILE}, ${CERTFILE}, ${FULLCHAINFILE}, ${CHAINFILE}"
}
invalid_challenge() {
local DOMAIN="${1}" RESPONSE="${2}"
}
request_failure() {
local STATUSCODE="${1}" REASON="${2}" REQTYPE="${3}"
}
exit_hook() {
:
}
HANDLER="$1"; shift
if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|deploy_cert|unchanged_cert|invalid_challenge|request_failure|exit_hook)$ ]]; then
"$HANDLER" "$@"
fi

[collapse]
For Proxmox 3.X

#!/usr/bin/env bash
set -e
set -u
set -o pipefail
export PROVIDER=${PROVIDER:-"yandex"}
export LEXICON_YANDEX_TOKEN=PLACE_TOKEN_HERE
deploy_challenge() {
local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
echo "deploy_challenge called: ${DOMAIN}, ${TOKEN_FILENAME}, ${TOKEN_VALUE}"
lexicon $PROVIDER create ${DOMAIN} TXT --name="_acme-challenge.${DOMAIN}." --content="${TOKEN_VALUE}"
sleep 30
}
clean_challenge() {
local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
echo "clean_challenge called: ${DOMAIN}, ${TOKEN_FILENAME}, ${TOKEN_VALUE}"
lexicon $PROVIDER delete ${DOMAIN} TXT --name="_acme-challenge.${DOMAIN}." --content="${TOKEN_VALUE}"
}
deploy_cert() {
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}"
echo "deploy_cert called: ${DOMAIN}, ${KEYFILE}, ${CERTFILE}, ${FULLCHAINFILE}, ${CHAINFILE}"
cp ${FULLCHAINFILE} /etc/pve/local/pve-ssl.pem
cp ${KEYFILE} /etc/pve/local/pve-ssl.key
service pveproxy restart
service pvedaemon restart
}
unchanged_cert() {
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}"
echo "unchanged_cert called: ${DOMAIN}, ${KEYFILE}, ${CERTFILE}, ${FULLCHAINFILE}, ${CHAINFILE}"
}
invalid_challenge() {
local DOMAIN="${1}" RESPONSE="${2}"
}
request_failure() {
local STATUSCODE="${1}" REASON="${2}" REQTYPE="${3}"
}
exit_hook() {
:
}
HANDLER="$1"; shift
if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|deploy_cert|unchanged_cert|invalid_challenge|request_failure|exit_hook)$ ]]; then
"$HANDLER" "$@"
fi

[collapse]

 

Add executable attribute:

chmod +x /opt/dehydrated/config/hook.sh

Run!

First run is registrations ACME client and acceptance of terms:

/opt/dehydrated/dehydrated --register --accept-terms

After them run dehydrated for certificate creation and deployment it to Proxmox:
/opt/dehydrated/dehydrated -c

And finally add certificate renovation to crontab. Run:
crontab -e

And add daily renew schedule:
@daily  /opt/dehydrated/dehydrated -c

Enjoy!