1. Получение сертификата Let’s Encrypt на VPS
1.1. Установка Certbot
Certbot — официальный клиент Let’s Encrypt. Установим его на VPS с публичным IP:
sudo apt update && sudo apt install certbot -y1.2. Выпуск сертификата
Проще всего использовать HTTP-валидацию (если домен привязан к VPS):
sudo certbot certonly --standalone -d example.comЕсли домен не привязан к VPS, подойдёт DNS-валидация (например, через Cloudflare API):
sudo certbot certonly --dns-cloudflare --dns-cloudflare-credentials ~/.secrets/cloudflare.ini -d example.comСертификаты сохранятся в /etc/letsencrypt/live/example.com/.
Автоматическое продление
Certbot по умолчанию настроен на автоматическое продление сертификатов Let’s Encrypt.
Как работает автоматическое продление?
- Срок действия сертификата Let’s Encrypt — 90 дней, но Certbot обычно пытается продлевать его каждые 60 дней (или чаще).
- Автоматическое обновление реализовано через systemd/cron, в зависимости от системы.
- В большинстве случаев Certbot добавляет задание в cron (или systemd timer), которое запускает
certbot renew. - Проверить наличие задания можно командой:
sudo crontab -l | grep certbotили для systemd:systemctl list-timers | grep certbot
- В большинстве случаев Certbot добавляет задание в cron (или systemd timer), которое запускает
- Команда
certbot renewпроверяет срок действия сертификатов и продлевает только те, которые истекают в течение 30 дней.
Как убедиться, что продление работает?
Вручную запустить пробное обновление (без реального продления):
sudo certbot renew --dry-runЕсли тест проходит успешно, значит автоматическое обновление настроено правильно.
Проверить логи последнего обновления
sudo grep certbot /var/log/syslogили
sudo journalctl -u certbotЧто делать, если автоматическое продление не настроено?
Если cron или systemd timer не добавлены, можно вручную создать задание (например, в cron):
sudo crontab -eДобавить строку (запуск каждые 12 часов + логирование):
0 */12 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"(Замените nginx на ваш веб-сервер, если используете Apache или другой.)
Важно!
- Убедитесь, что веб-сервер перезагружается после обновления (используйте
--post-hook). - Если сервер недоступен на 80/443 портах в момент обновления, продление может не сработать.
Если всё настроено правильно, Certbot будет автоматически продлевать сертификаты без вашего вмешательства. 🚀
2. Распространение сертификата на другие серверы
2.1. Ручное копирование (разовый способ)
Сертификаты можно скопировать вручную через scp:
scp -P SSH_PORT /etc/letsencrypt/live/example.com/* user@remote_server:/etc/ssl/proxmox/Но это неудобно при частых обновлениях.
2.2. Автоматизация через Certbot Hooks
Certbot поддерживает хуки (--deploy-hook), которые выполняются после обновления сертификата.
Создадим скрипт /etc/letsencrypt/renewal-hooks/deploy/copy_certs.sh:
#!/bin/bash
DOMAIN="example.com"
REMOTE_USER="user"
REMOTE_SERVERS=("server1" "server2")
SSH_PORT="22"
for server in "${REMOTE_SERVERS[@]}"; do
rsync -avz -e "ssh -p $SSH_PORT" \
/etc/letsencrypt/live/$DOMAIN/privkey.pem \
/etc/letsencrypt/live/$DOMAIN/fullchain.pem \
$REMOTE_USER@$server:/etc/ssl/proxmox/
ssh -p $SSH_PORT $REMOTE_USER@$server "systemctl reload nginx"
doneДаём права на выполнение:
chmod +x /etc/letsencrypt/renewal-hooks/deploy/copy_certs.shТеперь при каждом обновлении (certbot renew) сертификаты будут автоматически копироваться на указанные серверы.
3. Масштабируемое решение: Ansible
Если серверов много, ручное управление становится сложным. Ansible позволяет автоматизировать процесс централизованно.
3.1. Установка Ansible
На VPS:
sudo apt install ansible -y3.2. Создание инвентаря (inventory.ini)
[proxmox]
proxmox1 ansible_host=IP_1 ansible_port=SSH_PORT ansible_user=user
proxmox2 ansible_host=IP_2 ansible_port=SSH_PORT ansible_user=user
[homeassistantos]
haos1 ansible_host=IP_1 ansible_port=SSH_PORT ansible_user=user
haos2 ansible_host=IP_2 ansible_port=SSH_PORT ansible_user=user3.3. Playbook для развёртывания сертификатов (deploy_certs.yml)
Замените you.domain.com на свой
---
- name: Deploy Let's Encrypt certificates to remote hosts
hosts: all
become: true
gather_facts: false
vars:
local_cert_dir: "/etc/letsencrypt/live/example.duckdns.org"
group_proxmox: proxmox
group_haos: homeassistantos
tasks:
- name: Set variables for Proxmox
set_fact:
cert_target_dir: "/etc/pve/local"
cert_fullchain_name: "pveproxy-ssl.pem"
cert_privkey_name: "pveproxy-ssl.key"
cert_reload_command: "systemctl restart pveproxy"
when: inventory_hostname in groups[group_proxmox]
- name: Set variables for Home Assistant OS
set_fact:
cert_target_dir: "/ssl"
cert_fullchain_name: "fullchain.pem"
cert_privkey_name: "privkey.pem"
cert_reload_command: "ha core restart"
when: inventory_hostname in groups[group_haos]
- name: Copy fullchain.pem to Proxmox
copy:
src: "{{ local_cert_dir }}/fullchain.pem"
dest: "{{ cert_target_dir }}/{{ cert_fullchain_name }}"
force: true
when: inventory_hostname in groups[group_proxmox]
- name: Copy privkey.pem to Proxmox
copy:
src: "{{ local_cert_dir }}/privkey.pem"
dest: "{{ cert_target_dir }}/{{ cert_privkey_name }}"
force: true
when: inventory_hostname in groups[group_proxmox]
- name: Restart Proxmox service
shell: "{{ cert_reload_command }}"
when: inventory_hostname in groups[group_proxmox]
- name: Ensure HAOS SSL directory exists
shell: "ssh -p {{ ansible_port }} root@localhost 'mkdir -p {{ cert_target_dir }}'"
delegate_to: localhost
when: inventory_hostname in groups[group_haos]
- name: Copy fullchain.pem to HAOS
shell: "scp -P {{ ansible_port }} {{ local_cert_dir }}/fullchain.pem root@localhost:{{ cert_target_dir }}/{{ cert_fullchain_name }}"
delegate_to: localhost
when: inventory_hostname in groups[group_haos]
- name: Copy privkey.pem to HAOS
shell: "scp -P {{ ansible_port }} {{ local_cert_dir }}/privkey.pem root@localhost:{{ cert_target_dir }}/{{ cert_privkey_name }}"
delegate_to: localhost
when: inventory_hostname in groups[group_haos]
- name: Restart Home Assistant
shell: "ssh -p {{ ansible_port }} root@localhost '{{ cert_reload_command }}'"
delegate_to: localhost
when: inventory_hostname in groups[group_haos]
3.4. Запуск Playbook
ansible-playbook -i inventory.ini deploy_certs.yml3.5. Интеграция с Certbot
Добавим вызов Ansible в хук Certbot (/etc/letsencrypt/renewal-hooks/deploy/ansible_deploy.sh):
#!/bin/bash
ansible-playbook -i /path/to/inventory.ini /path/to/deploy_certs.ymlТеперь при каждом обновлении сертификата (certbot renew) Ansible будет разворачивать его на всех серверах.
4. Альтернативные методы
4.1. Общее хранилище (NFS/S3)
Если серверы имеют доступ к одному хранилищу, можно настроить синхронизацию:
rclone copy /etc/letsencrypt/live/example.com/ s3:bucket/certs/4.2. Использование ACME-клиента на каждой машине
Если синхронизация невозможна, можно настроить certbot или acme.sh на каждом сервере с DNS-валидацией.
Заключение
- Самый простой способ —
certbot + deploy-hookсrsync/scp. - Для масштабируемости — Ansible.
- Для распределённых систем — общее хранилище (S3/NFS).
Автоматизация экономит время и снижает риск ошибок. Настроив один раз, вы забудете о ручном управлении сертификатами!
Ещё варианты Playbook
Упрощённый Playbook для Proxmox (без валидации):
---
- name: Deploy SSL certificates
hosts: proxmox
become: yes
vars:
cert_domain: "you.domain.com"
tasks:
- name: Get Proxmox node name
command: hostname
register: proxmox_node
changed_when: false
- name: Install certificates (atomic method)
shell: |
install -m 644 -o root "/etc/letsencrypt/live/{{ cert_domain }}/fullchain.pem" "/tmp/fullchain.pem" &&
install -m 600 -o root "/etc/letsencrypt/live/{{ cert_domain }}/privkey.pem" "/tmp/privkey.pem" &&
cp -a "/tmp/fullchain.pem" "/etc/pve/nodes/{{ proxmox_node.stdout }}/pveproxy-ssl.pem" &&
cp -a "/tmp/privkey.pem" "/etc/pve/nodes/{{ proxmox_node.stdout }}/pveproxy-ssl.key" &&
chown root:www-data "/etc/pve/nodes/{{ proxmox_node.stdout }}/pveproxy-ssl.*"
args:
executable: /bin/bash
- name: Restart PVE services
systemctl:
name: "{{ item }}"
state: restarted
loop:
- pveproxy
- pvedaemonУниверсальная версия для любых систем:
---
- name: Universal SSL Deploy
hosts: all
become: yes
vars:
cert_domain: "you.domain.com"
cert_paths: # Определяем пути для разных ОС
proxmox:
cert: "/etc/pve/nodes/{{ ansible_hostname }}/pveproxy-ssl.pem"
key: "/etc/pve/nodes/{{ ansible_hostname }}/pveproxy-ssl.key"
services: ["pveproxy", "pvedaemon"]
debian:
cert: "/etc/ssl/certs/{{ cert_domain }}.pem"
key: "/etc/ssl/private/{{ cert_domain }}.key"
services: ["nginx"]
openwrt:
cert: "/etc/uhttpd.crt"
key: "/etc/uhttpd.key"
services: ["uhttpd"]
homeassistant:
cert: "/ssl/fullchain.pem" # Для HA в Docker
key: "/ssl/privkey.pem"
services: ["home-assistant"]
tasks:
- name: Deploy certificates
block:
- name: Copy certificate
copy:
src: "/etc/letsencrypt/live/{{ cert_domain }}/fullchain.pem"
dest: "{{ cert_paths[ansible_os_family | lower].cert | default(cert_paths['debian'].cert) }}"
mode: '0644'
owner: root
group: root
- name: Copy private key
copy:
src: "/etc/letsencrypt/live/{{ cert_domain }}/privkey.pem"
dest: "{{ cert_paths[ansible_os_family | lower].key | default(cert_paths['debian'].key) }}"
mode: '0600'
owner: root
group: root
when: "'proxmox' not in ansible_os_family | lower"
- name: Special handling for Proxmox
shell: |
install -m 644 -o root "/etc/letsencrypt/live/{{ cert_domain }}/fullchain.pem" "/tmp/fullchain.pem" &&
install -m 600 -o root "/etc/letsencrypt/live/{{ cert_domain }}/privkey.pem" "/tmp/privkey.pem" &&
cp -a "/tmp/fullchain.pem" "{{ cert_paths['proxmox'].cert }}" &&
cp -a "/tmp/privkey.pem" "{{ cert_paths['proxmox'].key }}" &&
chown root:www-data "{{ cert_paths['proxmox'].cert }}" "{{ cert_paths['proxmox'].key }}"
args:
executable: /bin/bash
when: "'proxmox' in ansible_os_family | lower"
- name: Restart services
systemd:
name: "{{ item }}"
state: restarted
loop: "{{ cert_paths[ansible_os_family | lower].services | default(cert_paths['debian'].services) }}"