4 min read

Nginx Proxy Manager и Let's Encrypt

У меня используется Nginx Proxy Manager(NPM) в качестве обратного прокси для моих сервисов в докере. Ранее я использовал связку DuckDNS и сертификат от LE. Идея простая: регистрируем бесплатный домен 3 уровня на DuckDNS, направляем его на наши хосты и в NPM получаем сертификат от LE методом dns challenge. Все максимально просто, нужно лишь указать свой ключ для доступа к апи DuckDNS, как результат получаем домены для всех домашних сервисов с ssl. По этому поводу если неплохое видео на youtube:

Но что делать если у нас есть домен находящийся на NS'ах с которыми NPM не может работать по api и нужно получить wcard сертификат?
Тут есть 2 варианта:
- перенести домен на сервера с которыми Let's Encrypt certbot умеет работать по апи.
- получить сертификат вручную.
Я решил пойти по второму пути.

Получение сертификата.

Так как NPM штатно не умеет получать сертификат от LE вручную для wildcard доменов, то придется делать руками это через консоль:
- Заходим в контейнер с NPM:

root@hub ~ # docker exec -it nginx_proxy /bin/bash
 _   _       _            ____                      __  __                                   
| \ | | __ _(_)_ __ __  _|  _ \ _ __ _____  ___   _|  \/  | __ _ _ __   __ _  __ _  ___ _ __ 
|  \| |/ _` | | '_ \\ \/ / |_) | '__/ _ \ \/ / | | | |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '__|
| |\  | (_| | | | | |>  <|  __/| | | (_) >  <| |_| | |  | | (_| | | | | (_| | (_| |  __/ |   
|_| \_|\__, |_|_| |_/_/\_\_|   |_|  \___/_/\_\\__, |_|  |_|\__,_|_| |_|\__,_|\__, |\___|_|   
       |___/                                  |___/                          |___/           

Version 2.11.3 (35d7a3a) 2024-07-01 11:42:06 UTC, OpenResty 1.21.4.3, debian 12 (bookworm), Certbot certbot 2.11.0
Base: debian:bookworm-slim, linux/amd64
Certbot: nginxproxymanager/nginx-full:latest, linux/amd64
Node: nginxproxymanager/nginx-full:certbot, linux/amd64

[root@docker-42520cfafb61:/app]# 

- Пробуем получить сертификат вручную:

[root@docker-42520cfafb61:/app]# certbot certonly --manual --preferred-challenges dns -d cave-labs.ru,*.cave-labs.ru

В процессе бот попросит нас добавить TXT запись с ключом для домена _acme-challenge.cave-labs.ru. После добавления ждем несколько минут пока обновятся dns и подтверждаем что запись добавлена, в конце бот сообщит что сертификат получен и покажет куда он его сохранил.

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/cave-labs.ru/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/cave-labs.ru/privkey.pem
This certificate expires on 2025-01-24.

Так у нас есть wcard сертификат от let's encrypt. Далее я вытащил ключ с сертификатом из контейнера и вручную добавил его в NPM через web

для основного домена я выпустил сертификат средствами самого NPM

Обновление сертификата.

Как же обновить сам сертификат если мы выпускали его вручную? Выпускать заново сертификат не нужно, certbot умеет обновлять текущий сертификат. Я это делал вручную:

[root@docker-42520cfafb61:/app]# certbot certonly --manual --expand --force-renewal --preferred-challenges dns -d cave-labs.ru,*.cave-labs.ru
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Renewing an existing certificate for cave-labs.ru and *.cave-labs.ru

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/cave-labs.ru/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/cave-labs.ru/privkey.pem
This certificate expires on 2025-01-24.
These files will be updated when the certificate renews.

NEXT STEPS:
- This certificate will not be renewed automatically. Autorenewal of --manual certificates requires the use of an authentication hook script (--manual-auth-hook) but one was not provided. To renew this certificate, repeat this same certbot command before the certificate's expiry date.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Ключ --expand дополнит уже существующий сертификат, а --force-renewal принудительно перезапишет файлы. В моем же случае можно создать отдельный контейнер с кроном и туда добавить туда задание на обновление сертификата либо обновлять сертификат вручную.

На этом можно было бы и остановиться, но в таком случае пришлось бы каждые 3 месяца перепрописывать новый сертификат для всех хостов которых может быть много. Поэтому я решил немного обмануть систему и сделать так чтоб при обновлении wcard сертификата мне не пришлось бы прописывать новый сертификат для каждого хоста отдельно. Тут есть 2 варианта:
- скопировать новый сертификат на место старого тем самым заменив его.
- сделать вместо сертификата ссылку на тот что получил certbot
Для начала смотрим где лежит сертификат установленный через web грепнув конфиги:

[root@docker-42520cfafb61:/app]# grep -rn ssl_certificate /data/nginx/proxy_host/ | awk '{print $3}' | sort | uniq -c
      8 /data/custom_ssl/npm-13/fullchain.pem;
      8 /data/custom_ssl/npm-13/privkey.pem;
      1 /etc/letsencrypt/live/npm-12/fullchain.pem;
      1 /etc/letsencrypt/live/npm-12/privkey.pem;

Нас интересует custom_ssl, на первое время я просто заменил сертификат на новый:

# cp -f /etc/letsencrypt/live/cave-labs.ru/fullchain.pem /data/custom_ssl/npm-13/fullchain.pem
cp: overwrite '/data/custom_ssl/npm-13/fullchain.pem'? y
# cp -f /etc/letsencrypt/live/cave-labs.ru/privkey.pem /data/custom_ssl/npm-13/privkey.pem
cp: overwrite '/data/custom_ssl/npm-13/privkey.pem'? y

Однако в данном случае придется каждый раз заменять сертификат. Для того чтобы этого избежать целесообразней сделать символьную ссылку:

[root@docker-42520cfafb61:/app]# rm /data/custom_ssl/npm-13/fullchain.pem 
rm: remove regular file '/data/custom_ssl/npm-13/fullchain.pem'? y
[root@docker-42520cfafb61:/app]# ln -s ../../../etc/letsencrypt/live/cave-labs.ru/fullchain.pem /data/custom_ssl/npm-13/fullchain.pem
[root@docker-42520cfafb61:/app]# rm /data/custom_ssl/npm-13/privkey.pem 
rm: remove regular file '/data/custom_ssl/npm-13/privkey.pem'? y
[root@docker-42520cfafb61:/app]# ln -s ../../../etc/letsencrypt/live/cave-labs.ru/privkey.pem /data/custom_ssl/npm-13/privkey.pem
[root@docker-42520cfafb61:/app]# ls -l
total 0
lrwxrwxrwx 1 root root 56 Oct 26 23:45 fullchain.pem -> ../../../etc/letsencrypt/live/cave-labs.ru/fullchain.pem
lrwxrwxrwx 1 root root 54 Oct 26 23:46 privkey.pem -> ../../../etc/letsencrypt/live/cave-labs.ru/privkey.pem

Документацию по certbot можно посмотреть тут.