Добавлен скрипт для автоматизации получения и обновления SSL сертификатов Let's Encrypt с использованием API reg.ru. Реализованы функции для работы с DNS-валидацией, логирования, проверки сертификатов и управления ими. Также создан bash-скрипт для упрощения процесса получения wildcard сертификата и обновления существующих сертификатов. Добавлена документация по настройке Nginx Proxy Manager с использованием полученного сертификата.

This commit is contained in:
Dmitriy Fofanov
2025-10-27 20:59:15 +03:00
parent f7de3d27b0
commit 07a65ffbba
8 changed files with 2498 additions and 51 deletions

View File

@@ -0,0 +1,56 @@
# Инструкция по созданию Let's Encrypt сертификата с DNS Challenge для провайдера reg.ru в Nginx Proxy Manager
---
## Предпосылки
- Доступ к Nginx Proxy Manager (NPM)
- Доступ к аккаунту reg.ru с правами управления DNS-записями
- API-ключ для управления DNS в reg.ru (если есть автоматическая интеграция)
- Нужно получить сертификат для `*.dfv24.com` (wildcard сертификат)
---
## Шаг 1. Получение API-ключа для reg.ru
1. Войдите в панель управления reg.ru
2. Перейдите в раздел управления API (если поддерживается)
3. Создайте или найдите API-ключ с правом редактирования DNS записей
4. Сохраните API-ключ и секрет (Client ID и API Token)
---
## Шаг 2. Настройка Nginx Proxy Manager для использования DNS Challenge reg.ru
1. В админке NPM перейдите в **SSL Certificates → Add SSL Certificate**
2. Выберите **Let's Encrypt** -> **DNS Challenge**
3. В поле **Provider** выберите `reg_ru` или `custom` (если провайдера нет, потребуется писать скрипт)
4. В поля API впишите необходимые параметры:
- Client ID
- API Token
5. В поле **Domain Names** укажите:
`*.dfv24.com` (для wildcard сертификата)
и основной домен `dfv24.com`
6. Включите остальные опции (Terms of Service, Email)
7. Нажмите **Save** для запроса сертификата
8. NPM автоматически добавит DNS TXT-записи для подтверждения владения доменом через API reg.ru
---
## Шаг 3. Проверка и автоматическое продление
- После успешного создания сертификата NPM будет автоматически обновлять его через DNS Challenge.
- Для успешного продления важно, чтобы API-ключ был действующим, а NPM имел доступ к DNS управлению.
---
## Если в NPM нет готовой интеграции с reg.ru
- Используйте внешний скрипт для обновления TXT записей DNS в reg.ru, настраиваемый в NPM через **Custom DNS Provider**.
- Нужна настройка curl-запросов к API reg.ru для добавления/удаления TXT записей.
---
# Итог
Для wildcard сертификатов Let's Encrypt у reg.ru необходимо использовать DNS Challenge, используя API провайдера для автоматического управления DNS записями.
В Nginx Proxy Manager настройте DNS Challenge с учётом особенностей reg.ru для бесшовного получения и продления сертификатов.

555
README.md
View File

@@ -1,79 +1,532 @@
# Подробная инструкция по настройке Nginx Proxy Manager с одним глобальным SSL сертификатом для всех доменов dfv24.com # Руководство по использованию скриптов для управления SSL сертификатами Let's Encrypt с DNS-валидацией через API reg.ru
## Предпосылки ## Содержание
- Установлен и запущен [Nginx Proxy Manager](http://192.168.10.14:81/) 1. [Введение](#введение)
- Основной домен: dfv24.com 2. [Требования](#требования)
- Хостинг и DNS записи домена находятся на reg.ru 3. [Установка зависимостей](#установка-зависимостей)
- Нужно использовать один SSL сертификат (например, wildcard) для всех поддоменов dfv24.com 4. [Настройка](#настройка)
5. [Использование Bash скрипта](#использование-bash-скрипта)
6. [Использование Python скрипта](#использование-python-скрипта)
7. [Автоматизация обновления](#автоматизация-обновления)
8. [Устранение неполадок](#устранение-неполадок)
--- ---
## Шаг 1. Покупка и получение SSL Wildcard сертификата для dfv24.com ## Введение
1. На reg.ru или любом другом удостоверяющем центре (CA) закажите wildcard сертификат на домен вида `*.dfv24.com`.
2. Получите файлы сертификата: В проекте представлены два скрипта для автоматического создания и обновления SSL сертификатов Let's Encrypt с использованием DNS-валидации через API reg.ru:
- Главный сертификат (CRT)
- Промежуточные сертификаты (CA Bundle) 1. **letsencrypt_regru_dns.sh** - Bash скрипт с использованием плагина certbot-dns-regru
- Приватный ключ (KEY) 2. **letsencrypt_regru_api.py** - Python скрипт с прямым взаимодействием с API reg.ru
Оба скрипта поддерживают:
- Создание wildcard сертификатов (*.domain.com)
- Автоматическое обновление сертификатов
- DNS-валидацию через API reg.ru
- Логирование всех операций
- Перезагрузку веб-сервера после обновления
--- ---
## Шаг 2. Импорт вашего SSL сертификата в Nginx Proxy Manager ## Требования
1. Авторизуйтесь в Nginx Proxy Manager на http://192.168.10.14:81/
2. Перейдите в раздел **SSL Certificates** → кнопку **Add SSL Certificate** ### Общие требования
3. Выберите **Custom** (пользовательский сертификат) - Операционная система: Linux (Ubuntu/Debian/CentOS)
4. В поля вставьте: - Права: root или sudo
- **Certificate** — основной CRT + CA Bundle (если CA Bundle раздельно, склейте в один файл или вставляйте последовательно) - Домен зарегистрирован на reg.ru
- **Key** — содержимое приватного ключа - Доступ к API reg.ru (имя пользователя и пароль)
- Имя сертификата задайте, например, `dfv24_wildcard`
5. Сохраните ### Для Bash скрипта
- certbot
- certbot-dns-regru (плагин)
- curl
- jq
- openssl
### Для Python скрипта
- Python 3.6+
- certbot
- pip3
- Модули Python: requests, cryptography
--- ---
## Шаг 3. Настройка прокси-хостов с использованием глобального сертификата ## Установка зависимостей
1. Перейдите в **Proxy Hosts****Add Proxy Host** ### Ubuntu/Debian
2. Заполните поля:
- **Domain Names**: Например, `sub1.dfv24.com` (для первого поддомена)
- **Scheme**: http или https, в зависимости от бекенда
- **Forward Hostname / IP**: IP или DNS адрес вашего внутреннего сервиса
- **Forward Port**: порт сервиса (например, 80 или 443)
3. Включите **SSL** → Отметьте **Use a shared SSL certificate** (если такая опция доступна) или выберите ранее импортированный сертификат из списка
4. Активируйте: **Block Common Exploits**, **Websockets Support**, выставьте Redirect HTTP to HTTPS, если требуется
5. Сохраните прокси-хост
6. Повторите для всех поддоменов, указывая нужные домены и выбирая тот же wildcard SSL сертификат ```bash
# Обновление пакетов
sudo apt-get update
# Установка базовых зависимостей
sudo apt-get install -y certbot curl jq openssl python3 python3-pip
# Для Python скрипта
sudo pip3 install certbot-dns-regru requests cryptography
# Для Bash скрипта (если используете)
sudo pip3 install certbot-dns-regru
```
### CentOS/RHEL
```bash
# Обновление пакетов
sudo yum update -y
# Установка EPEL репозитория
sudo yum install -y epel-release
# Установка базовых зависимостей
sudo yum install -y certbot curl jq openssl python3 python3-pip
# Установка плагина
sudo pip3 install certbot-dns-regru requests cryptography
```
--- ---
## Шаг 4. Настройка DNS записей на reg.ru ## Настройка
1. Войдите в панель управления доменом на reg.ru ### 1. Получение учетных данных API reg.ru
2. Создайте или отредактируйте DNS записи типа A:
- `dfv24.com` → IP вашего Nginx Proxy Manager (например, 192.168.10.14) 1. Войдите в личный кабинет на сайте [reg.ru](https://www.reg.ru)
- `*.dfv24.com` → тот же IP или конкретные поддомены, если есть специальные 2. Перейдите в раздел "Управление API"
3. Сохраните изменения 3. Используйте ваше имя пользователя и пароль для доступа к API
4. Дождитесь обновления DNS (от нескольких минут до 24 часов) 4. Убедитесь, что у вас есть права на управление DNS-записями
### 2. Настройка конфигурации
#### Для Python скрипта
Создайте файл конфигурации на основе примера:
```bash
# Создание примера конфигурации
sudo python3 letsencrypt_regru_api.py --create-config /etc/letsencrypt/regru_config.json
# Редактирование конфигурации
sudo nano /etc/letsencrypt/regru_config.json
```
Отредактируйте параметры:
```json
{
"regru_username": "your_actual_username",
"regru_password": "your_actual_password",
"domain": "dfv24.com",
"wildcard": true,
"email": "admin@dfv24.com",
"cert_dir": "/etc/letsencrypt/live",
"log_file": "/var/log/letsencrypt_regru.log",
"dns_propagation_wait": 60,
"dns_check_attempts": 10,
"dns_check_interval": 10
}
```
#### Для Bash скрипта
Отредактируйте переменные в начале скрипта:
```bash
sudo nano letsencrypt_regru_dns.sh
```
Измените следующие параметры:
```bash
REGRU_USERNAME="your_actual_username"
REGRU_PASSWORD="your_actual_password"
DOMAIN="dfv24.com"
WILDCARD_DOMAIN="*.dfv24.com"
EMAIL="admin@dfv24.com"
```
### 3. Установка прав доступа
```bash
# Для Bash скрипта
sudo chmod +x letsencrypt_regru_dns.sh
# Для Python скрипта
sudo chmod +x letsencrypt_regru_api.py
# Защита файла конфигурации
sudo chmod 600 /etc/letsencrypt/regru_config.json
```
--- ---
## Шаг 5. Тест и проверка работы ## Использование Bash скрипта
1. В браузере откройте любой из поддоменов `https://sub1.dfv24.com` ### Первый запуск (получение сертификата)
2. Сертификат должен быть валидным, выданным на wildcard `*.dfv24.com`
3. Проверьте работу прокси и корректность подстановки сертификата ```bash
4. При необходимости проверьте логи Nginx Proxy Manager и исправьте ошибки sudo ./letsencrypt_regru_dns.sh
```
Скрипт автоматически:
1. Проверит зависимости
2. Создаст файл с учетными данными
3. Установит плагин certbot-dns-regru (если не установлен)
4. Проверит наличие существующего сертификата
5. Получит новый сертификат или обновит существующий
6. Перезагрузит веб-сервер
### Просмотр логов
```bash
sudo tail -f /var/log/letsencrypt_regru.log
```
### Ручная проверка сертификата
```bash
sudo certbot certificates
```
--- ---
## Дополнительно ## Использование Python скрипта
- Если в Nginx Proxy Manager нет GUI опции выбора общего сертификата, можно вручную сконфигурировать конфиги через директорий `/data/nginx/proxy_host` и прописать SSL сертификат для всех хостов. ### Создание примера конфигурации
- При обновлении сертификата — повторно импортировать его в Nginx Proxy Manager.
- Можно использовать LetsEncrypt для автоматического получения wildcard сертификата с помощью DNS-валидации (если поддерживается вашим DNS-провайдером). ```bash
sudo python3 letsencrypt_regru_api.py --create-config /etc/letsencrypt/regru_config.json
```
### Получение нового сертификата
```bash
# С использованием конфигурационного файла
sudo python3 letsencrypt_regru_api.py -c /etc/letsencrypt/regru_config.json --obtain
# С подробным выводом
sudo python3 letsencrypt_regru_api.py -c /etc/letsencrypt/regru_config.json --obtain -v
```
### Обновление существующего сертификата
```bash
sudo python3 letsencrypt_regru_api.py -c /etc/letsencrypt/regru_config.json --renew
```
### Проверка срока действия сертификата
```bash
sudo python3 letsencrypt_regru_api.py -c /etc/letsencrypt/regru_config.json --check
```
### Автоматический режим (проверка и обновление)
```bash
# Скрипт сам определит, нужно ли обновление
sudo python3 letsencrypt_regru_api.py -c /etc/letsencrypt/regru_config.json
```
### Опции командной строки
```
-c, --config FILE Путь к файлу конфигурации (JSON)
--create-config FILE Создать пример файла конфигурации
--obtain Получить новый сертификат
--renew Обновить существующий сертификат
--check Проверить срок действия сертификата
-v, --verbose Подробный вывод
```
--- ---
# Итог ## Автоматизация обновления
Используйте один wildcard сертификат для всех поддоменов, импортируйте его как пользовательский сертификат в Nginx Proxy Manager, при создании прокси-хостов выбирайте его в настройках SSL. Управляйте DNS записями на reg.ru, направляя domен на IP Nginx Proxy Manager. Let's Encrypt сертификаты действительны 90 дней. Рекомендуется настроить автоматическое обновление.
Это позволит юридически использовать единый сертификат для всех сервисов с различными поддоменами под вашим доменом dfv24.com.
### Использование cron (для обоих скриптов)
#### Для Python скрипта
```bash
# Открыть crontab
sudo crontab -e
# Добавить задачу (проверка каждый день в 3:00 AM)
0 3 * * * /usr/bin/python3 /path/to/letsencrypt_regru_api.py -c /etc/letsencrypt/regru_config.json >> /var/log/letsencrypt_cron.log 2>&1
```
#### Для Bash скрипта
```bash
# Открыть crontab
sudo crontab -e
# Добавить задачу (проверка каждый день в 3:00 AM)
0 3 * * * /path/to/letsencrypt_regru_dns.sh >> /var/log/letsencrypt_cron.log 2>&1
```
### Использование systemd timer
Создайте systemd service и timer для более гибкого управления:
#### Создание service файла
```bash
sudo nano /etc/systemd/system/letsencrypt-regru.service
```
Содержимое:
```ini
[Unit]
Description=Let's Encrypt Certificate Renewal with reg.ru DNS
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/python3 /path/to/letsencrypt_regru_api.py -c /etc/letsencrypt/regru_config.json
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
```
#### Создание timer файла
```bash
sudo nano /etc/systemd/system/letsencrypt-regru.timer
```
Содержимое:
```ini
[Unit]
Description=Daily Let's Encrypt Certificate Check and Renewal
Requires=letsencrypt-regru.service
[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=1h
[Install]
WantedBy=timers.target
```
#### Активация timer
```bash
# Перезагрузка systemd
sudo systemctl daemon-reload
# Включение и запуск timer
sudo systemctl enable letsencrypt-regru.timer
sudo systemctl start letsencrypt-regru.timer
# Проверка статуса
sudo systemctl status letsencrypt-regru.timer
sudo systemctl list-timers
```
---
## Устранение неполадок
### Проблема: Ошибка аутентификации API reg.ru
**Решение:**
- Проверьте правильность имени пользователя и пароля
- Убедитесь, что у вашего аккаунта есть доступ к API
- Проверьте, что домен находится под управлением вашего аккаунта
```bash
# Проверка доступа к API
curl -X POST "https://api.reg.ru/api/regru2/user/get_balance" \
-d "username=YOUR_USERNAME" \
-d "password=YOUR_PASSWORD" \
-d "output_format=json"
```
### Проблема: DNS запись не распространяется
**Решение:**
- Увеличьте параметр `dns_propagation_wait` в конфигурации (например, до 120 секунд)
- Проверьте DNS записи вручную:
```bash
nslookup -type=TXT _acme-challenge.dfv24.com
# или
dig TXT _acme-challenge.dfv24.com
```
### Проблема: Certbot не установлен
**Решение:**
```bash
# Ubuntu/Debian
sudo apt-get install certbot
# CentOS/RHEL
sudo yum install certbot
# Или через snap
sudo snap install --classic certbot
```
### Проблема: Плагин certbot-dns-regru не найден
**Решение:**
```bash
# Установка через pip
sudo pip3 install certbot-dns-regru
# Или через pip3 с обновлением
sudo pip3 install --upgrade certbot-dns-regru
```
### Проблема: Недостаточно прав
**Решение:**
- Убедитесь, что запускаете скрипт от имени root или с sudo
- Проверьте права доступа к директории `/etc/letsencrypt/`
```bash
sudo chmod 755 /etc/letsencrypt/
sudo chown -R root:root /etc/letsencrypt/
```
### Проблема: Ошибка при перезагрузке веб-сервера
**Решение:**
- Проверьте, какой веб-сервер используется:
```bash
systemctl status nginx
systemctl status apache2
```
- Вручную перезагрузите веб-сервер:
```bash
# Для Nginx
sudo systemctl reload nginx
# Для Apache
sudo systemctl reload apache2
```
### Просмотр подробных логов certbot
```bash
# Логи certbot
sudo tail -f /var/log/letsencrypt/letsencrypt.log
# Логи скрипта
sudo tail -f /var/log/letsencrypt_regru.log
```
### Тестовый режим (staging)
Для тестирования используйте staging окружение Let's Encrypt:
```bash
# Добавьте опцию --staging к команде certbot
certbot certonly --staging --dns-regru ...
```
---
## Проверка полученного сертификата
### Просмотр информации о сертификате
```bash
# Просмотр всех сертификатов
sudo certbot certificates
# Просмотр конкретного сертификата
sudo openssl x509 -in /etc/letsencrypt/live/dfv24.com/cert.pem -text -noout
# Проверка даты истечения
sudo openssl x509 -enddate -noout -in /etc/letsencrypt/live/dfv24.com/cert.pem
```
### Проверка сертификата в браузере
1. Откройте ваш сайт в браузере: `https://dfv24.com`
2. Нажмите на иконку замка в адресной строке
3. Проверьте информацию о сертификате
4. Убедитесь, что сертификат выдан Let's Encrypt и покрывает wildcard домен
### Онлайн проверка SSL
- [SSL Labs](https://www.ssllabs.com/ssltest/)
- [SSL Shopper](https://www.sslshopper.com/ssl-checker.html)
---
## Использование сертификата в Nginx Proxy Manager
После получения сертификата вы можете использовать его в Nginx Proxy Manager:
### Вариант 1: Импорт существующего сертификата
1. Войдите в Nginx Proxy Manager: http://192.168.10.14:81/
2. Перейдите в **SSL Certificates****Add SSL Certificate**
3. Выберите **Custom**
4. Вставьте содержимое файлов:
- Certificate Key: `/etc/letsencrypt/live/dfv24.com/privkey.pem`
- Certificate: `/etc/letsencrypt/live/dfv24.com/fullchain.pem`
5. Сохраните сертификат
### Вариант 2: Прямая настройка Nginx
Если вы используете Nginx напрямую, добавьте в конфигурацию:
```nginx
server {
listen 443 ssl http2;
server_name dfv24.com *.dfv24.com;
ssl_certificate /etc/letsencrypt/live/dfv24.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dfv24.com/privkey.pem;
# Дополнительные SSL настройки
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# ... остальная конфигурация
}
```
---
## Поддержка и вопросы
При возникновении проблем:
1. Проверьте логи: `/var/log/letsencrypt_regru.log`
2. Проверьте логи certbot: `/var/log/letsencrypt/letsencrypt.log`
3. Используйте подробный режим: `-v` для Python скрипта
4. Проверьте документацию reg.ru API: https://www.reg.ru/support/api
5. Проверьте документацию Let's Encrypt: https://letsencrypt.org/docs/
---
## Заключение
Оба скрипта предоставляют надежное решение для автоматического управления SSL сертификатами Let's Encrypt с использованием DNS-валидации через API reg.ru.
**Рекомендации:**
- Используйте Python скрипт для более гибкой настройки и интеграции
- Используйте Bash скрипт для простоты и минимальных зависимостей
- Настройте автоматическое обновление через cron или systemd timer
- Регулярно проверяйте логи и статус сертификатов
- Храните учетные данные в безопасности (chmod 600)
Успешной автоматизации! 🔒

170
SSL_SCRIPTS_README.md Normal file
View File

@@ -0,0 +1,170 @@
# Автоматизация SSL сертификатов Let's Encrypt для reg.ru
Набор скриптов для автоматического создания и обновления SSL сертификатов Let's Encrypt с использованием DNS-валидации через API reg.ru.
## 📁 Содержимое проекта
- **letsencrypt_regru_dns.sh** - Bash скрипт (Linux) с certbot-dns-regru
- **letsencrypt_regru_api.py** - Python скрипт с прямым API интеграцией
- **letsencrypt_regru.ps1** - PowerShell скрипт (Windows)
- **config.json.example** - Пример файла конфигурации
- **USAGE.md** - Подробная инструкция по использованию
## 🚀 Быстрый старт
### Linux (Bash)
```bash
# 1. Отредактируйте конфигурацию в скрипте
nano letsencrypt_regru_dns.sh
# 2. Установите права
chmod +x letsencrypt_regru_dns.sh
# 3. Запустите
sudo ./letsencrypt_regru_dns.sh
```
### Linux (Python)
```bash
# 1. Создайте конфигурацию
sudo python3 letsencrypt_regru_api.py --create-config /etc/letsencrypt/regru_config.json
# 2. Отредактируйте конфигурацию
sudo nano /etc/letsencrypt/regru_config.json
# 3. Получите сертификат
sudo python3 letsencrypt_regru_api.py -c /etc/letsencrypt/regru_config.json --obtain
```
### Windows (PowerShell)
```powershell
# 1. Создайте файл конфигурации config.json на основе config.json.example
# 2. Запустите скрипт
.\letsencrypt_regru.ps1 -ConfigFile .\config.json
```
## ⚙️ Конфигурация
Отредактируйте `config.json`:
```json
{
"regru_username": аш_логин",
"regru_password": аш_пароль",
"domain": "dfv24.com",
"wildcard": true,
"email": "admin@dfv24.com"
}
```
## 📋 Требования
### Linux
- certbot
- Python 3.6+
- pip3
- requests, cryptography (Python модули)
- certbot-dns-regru (опционально)
### Windows
- certbot
- PowerShell 5.1+
- openssl (для проверки сертификатов)
## 🔄 Автоматическое обновление
### Через cron (Linux)
```bash
# Добавьте в crontab
sudo crontab -e
# Проверка каждый день в 3:00
0 3 * * * /usr/bin/python3 /path/to/letsencrypt_regru_api.py -c /etc/letsencrypt/regru_config.json
```
### Через Task Scheduler (Windows)
1. Откройте Task Scheduler
2. Создайте новую задачу
3. Триггер: Ежедневно в 3:00
4. Действие: Запуск PowerShell скрипта
## 📖 Функции
✅ Создание wildcard сертификатов (*.domain.com)
✅ Автоматическая DNS-валидация через API reg.ru
✅ Проверка срока действия сертификата
✅ Автоматическое обновление перед истечением
✅ Перезагрузка веб-сервера после обновления
✅ Подробное логирование всех операций
## 🔧 Использование с Nginx Proxy Manager
После получения сертификата:
1. Войдите в NPM: http://192.168.10.14:81/
2. SSL Certificates → Add SSL Certificate → Custom
3. Вставьте содержимое:
- Certificate Key: `/etc/letsencrypt/live/domain.com/privkey.pem`
- Certificate: `/etc/letsencrypt/live/domain.com/fullchain.pem`
## 📝 Логи
- Bash: `/var/log/letsencrypt_regru.log`
- Python: `/var/log/letsencrypt_regru.log`
- PowerShell: `.\letsencrypt_regru.log`
- Certbot: `/var/log/letsencrypt/letsencrypt.log`
## 🆘 Устранение неполадок
### Ошибка аутентификации API
- Проверьте учетные данные reg.ru
- Убедитесь, что домен под вашим управлением
### DNS запись не распространяется
- Увеличьте `dns_propagation_wait` до 120 секунд
- Проверьте DNS: `nslookup -type=TXT _acme-challenge.domain.com`
### Certbot не найден
```bash
# Ubuntu/Debian
sudo apt-get install certbot
# Или через snap
sudo snap install --classic certbot
```
## 📚 Документация
Подробная документация в файле [USAGE.md](USAGE.md)
## 🔐 Безопасность
- Храните учетные данные в безопасности
- Используйте `chmod 600` для конфигурационных файлов
- Регулярно обновляйте пароли
## ⚠️ Важно
- Let's Encrypt сертификаты действительны 90 дней
- Рекомендуется настроить автоматическое обновление
- Для wildcard сертификатов требуется DNS-валидация
## 📞 Поддержка
- [Документация reg.ru API](https://www.reg.ru/support/api)
- [Документация Let's Encrypt](https://letsencrypt.org/docs/)
- [Certbot Documentation](https://certbot.eff.org/docs/)
## 📄 Лицензия
Скрипты предоставляются "как есть" для свободного использования.
---
**Успешной автоматизации! 🔒**

12
config.json.example Normal file
View File

@@ -0,0 +1,12 @@
{
"regru_username": "your_username",
"regru_password": "your_password",
"domain": "dfv24.com",
"wildcard": true,
"email": "admin@dfv24.com",
"cert_dir": "/etc/letsencrypt/live",
"log_file": "/var/log/letsencrypt_regru.log",
"dns_propagation_wait": 60,
"dns_check_attempts": 10,
"dns_check_interval": 10
}

556
letsencrypt_regru.ps1 Normal file
View File

@@ -0,0 +1,556 @@
# ============================================================================
# Скрипт для создания и обновления SSL сертификата Let's Encrypt
# с использованием DNS-валидации через API reg.ru (PowerShell версия)
#
# Автор: GitHub Copilot
# Дата: 27.10.2025
# Описание: PowerShell версия скрипта для Windows окружения
# ============================================================================
<#
.SYNOPSIS
Автоматизация получения SSL сертификатов Let's Encrypt через DNS-валидацию reg.ru
.DESCRIPTION
Скрипт позволяет автоматически получать и обновлять SSL сертификаты Let's Encrypt
для доменов на reg.ru используя DNS-01 challenge через API
.PARAMETER Domain
Основной домен (например, dfv24.com)
.PARAMETER Email
Email для уведомлений Let's Encrypt
.PARAMETER RegRuUsername
Имя пользователя reg.ru
.PARAMETER RegRuPassword
Пароль reg.ru
.PARAMETER Wildcard
Создать wildcard сертификат (*.domain.com)
.PARAMETER ConfigFile
Путь к файлу конфигурации JSON
.EXAMPLE
.\letsencrypt_regru.ps1 -Domain "dfv24.com" -Email "admin@dfv24.com" -Wildcard
#>
param(
[Parameter(Mandatory=$false)]
[string]$Domain = "dfv24.com",
[Parameter(Mandatory=$false)]
[string]$Email = "admin@dfv24.com",
[Parameter(Mandatory=$false)]
[string]$RegRuUsername = "",
[Parameter(Mandatory=$false)]
[string]$RegRuPassword = "",
[Parameter(Mandatory=$false)]
[switch]$Wildcard = $true,
[Parameter(Mandatory=$false)]
[string]$ConfigFile = ".\config.json",
[Parameter(Mandatory=$false)]
[switch]$Verbose = $false
)
# ============================================================================
# КОНФИГУРАЦИЯ
# ============================================================================
$Script:Config = @{
RegRuApiUrl = "https://api.reg.ru/api/regru2"
CertbotPath = "certbot"
LogFile = ".\letsencrypt_regru.log"
DnsPropagationWait = 60
DnsCheckAttempts = 10
DnsCheckInterval = 10
}
# ============================================================================
# ФУНКЦИИ ЛОГИРОВАНИЯ
# ============================================================================
function Write-Log {
param(
[string]$Message,
[string]$Level = "INFO"
)
$Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$LogMessage = "[$Timestamp] [$Level] $Message"
# Вывод в консоль
switch ($Level) {
"ERROR" { Write-Host $LogMessage -ForegroundColor Red }
"WARNING" { Write-Host $LogMessage -ForegroundColor Yellow }
"SUCCESS" { Write-Host $LogMessage -ForegroundColor Green }
default { Write-Host $LogMessage }
}
# Запись в файл
Add-Content -Path $Script:Config.LogFile -Value $LogMessage
}
# ============================================================================
# ФУНКЦИИ РАБОТЫ С КОНФИГУРАЦИЕЙ
# ============================================================================
function Load-Configuration {
param([string]$ConfigPath)
Write-Log "Загрузка конфигурации из: $ConfigPath"
if (Test-Path $ConfigPath) {
try {
$config = Get-Content -Path $ConfigPath -Raw | ConvertFrom-Json
# Обновляем параметры скрипта из конфигурации
if ($config.domain) { $Script:Domain = $config.domain }
if ($config.email) { $Script:Email = $config.email }
if ($config.regru_username) { $Script:RegRuUsername = $config.regru_username }
if ($config.regru_password) { $Script:RegRuPassword = $config.regru_password }
if ($null -ne $config.wildcard) { $Script:Wildcard = $config.wildcard }
Write-Log "Конфигурация успешно загружена" "SUCCESS"
return $true
}
catch {
Write-Log "Ошибка при загрузке конфигурации: $_" "ERROR"
return $false
}
}
else {
Write-Log "Файл конфигурации не найден: $ConfigPath" "WARNING"
return $false
}
}
function Create-SampleConfig {
param([string]$OutputPath = ".\config.json")
$sampleConfig = @{
regru_username = "your_username"
regru_password = "your_password"
domain = "dfv24.com"
wildcard = $true
email = "admin@dfv24.com"
dns_propagation_wait = 60
dns_check_attempts = 10
dns_check_interval = 10
}
$sampleConfig | ConvertTo-Json -Depth 10 | Set-Content -Path $OutputPath
Write-Log "Пример конфигурации создан: $OutputPath" "SUCCESS"
}
# ============================================================================
# ФУНКЦИИ РАБОТЫ С REG.RU API
# ============================================================================
function Invoke-RegRuApi {
param(
[string]$Method,
[hashtable]$Params
)
$url = "$($Script:Config.RegRuApiUrl)/$Method"
# Добавляем учетные данные
$Params["username"] = $Script:RegRuUsername
$Params["password"] = $Script:RegRuPassword
$Params["output_format"] = "json"
Write-Log "Отправка запроса к API: $Method" "DEBUG"
try {
$response = Invoke-RestMethod -Uri $url -Method Post -Body $Params -ContentType "application/x-www-form-urlencoded"
if ($response.result -eq "success") {
Write-Log "Запрос $Method выполнен успешно" "DEBUG"
return $response
}
else {
$errorMsg = if ($response.error_text) { $response.error_text } else { "Неизвестная ошибка" }
Write-Log "Ошибка API: $errorMsg" "ERROR"
throw "API Error: $errorMsg"
}
}
catch {
Write-Log "Ошибка HTTP запроса: $_" "ERROR"
throw
}
}
function Get-DnsRecords {
param([string]$Domain)
Write-Log "Получение DNS записей для домена: $Domain"
$params = @{
domain = $Domain
}
$response = Invoke-RegRuApi -Method "zone/get_resource_records" -Params $params
if ($response.answer -and $response.answer.records) {
$records = $response.answer.records
Write-Log "Получено $($records.Count) DNS записей"
return $records
}
else {
Write-Log "DNS записи не найдены" "WARNING"
return @()
}
}
function Add-TxtRecord {
param(
[string]$Domain,
[string]$Subdomain,
[string]$TxtValue
)
Write-Log "Добавление TXT записи: $Subdomain.$Domain = $TxtValue"
$params = @{
domain = $Domain
subdomain = $Subdomain
text = $TxtValue
output_content_type = "plain"
}
try {
$null = Invoke-RegRuApi -Method "zone/add_txt" -Params $params
Write-Log "TXT запись успешно добавлена" "SUCCESS"
return $true
}
catch {
Write-Log "Не удалось добавить TXT запись: $_" "ERROR"
return $false
}
}
function Remove-TxtRecord {
param(
[string]$Domain,
[string]$Subdomain,
[string]$TxtValue
)
Write-Log "Удаление TXT записи: $Subdomain.$Domain"
# Получаем список записей
$records = Get-DnsRecords -Domain $Domain
# Ищем нужную TXT запись
$record = $records | Where-Object {
$_.rectype -eq "TXT" -and
$_.subdomain -eq $Subdomain -and
$_.text -eq $TxtValue
} | Select-Object -First 1
if (-not $record) {
Write-Log "TXT запись для удаления не найдена" "WARNING"
return $false
}
$params = @{
domain = $Domain
record_id = $record.id
}
try {
$null = Invoke-RegRuApi -Method "zone/remove_record" -Params $params
Write-Log "TXT запись успешно удалена" "SUCCESS"
return $true
}
catch {
Write-Log "Не удалось удалить TXT запись: $_" "ERROR"
return $false
}
}
# ============================================================================
# ФУНКЦИИ РАБОТЫ С CERTBOT
# ============================================================================
function Test-CertbotInstalled {
try {
$version = & certbot --version 2>&1
Write-Log "Certbot установлен: $version"
return $true
}
catch {
Write-Log "Certbot не установлен!" "ERROR"
Write-Log "Установите Certbot: https://certbot.eff.org/instructions" "ERROR"
return $false
}
}
function Get-CertificateExpiry {
param([string]$Domain)
$certPath = Join-Path $env:ProgramData "letsencrypt\live\$Domain\cert.pem"
if (-not (Test-Path $certPath)) {
Write-Log "Сертификат не найден: $certPath"
return $null
}
try {
# Используем openssl для проверки сертификата
$expiryText = & openssl x509 -enddate -noout -in $certPath 2>&1
if ($expiryText -match "notAfter=(.+)") {
$expiryDate = [DateTime]::Parse($matches[1])
$daysLeft = ($expiryDate - (Get-Date)).Days
Write-Log "Сертификат истекает: $($expiryDate.ToString('yyyy-MM-dd'))"
Write-Log "Осталось дней: $daysLeft"
return $daysLeft
}
}
catch {
Write-Log "Ошибка при проверке сертификата: $_" "ERROR"
}
return $null
}
function Invoke-DnsChallenge {
param(
[string]$Domain,
[string]$ValidationToken
)
Write-Log "=== DNS Challenge: Добавление TXT записи ===" "INFO"
# Извлекаем поддомен
$subdomain = "_acme-challenge"
# Добавляем TXT запись
$success = Add-TxtRecord -Domain $Script:Domain -Subdomain $subdomain -TxtValue $ValidationToken
if ($success) {
# Ждем распространения DNS
$waitTime = $Script:Config.DnsPropagationWait
Write-Log "Ожидание распространения DNS ($waitTime секунд)..."
Start-Sleep -Seconds $waitTime
Write-Log "DNS валидация готова" "SUCCESS"
return $true
}
return $false
}
function Invoke-DnsCleanup {
param(
[string]$Domain,
[string]$ValidationToken
)
Write-Log "=== DNS Challenge: Удаление TXT записи ===" "INFO"
$subdomain = "_acme-challenge"
return Remove-TxtRecord -Domain $Script:Domain -Subdomain $subdomain -TxtValue $ValidationToken
}
# ============================================================================
# ГЛАВНЫЕ ФУНКЦИИ
# ============================================================================
function Get-Certificate {
param([string]$Domain, [bool]$WildcardCert)
Write-Log "=== Запрос нового SSL сертификата ===" "INFO"
# Формируем список доменов
$domains = @($Domain)
if ($WildcardCert) {
$domains += "*.$Domain"
}
$domainArgs = @()
foreach ($d in $domains) {
$domainArgs += "-d"
$domainArgs += $d
}
Write-Log "Домены для сертификата: $($domains -join ', ')"
# Создаем временные скрипты для хуков
$authHookScript = Join-Path $env:TEMP "certbot_auth_hook.ps1"
$cleanupHookScript = Join-Path $env:TEMP "certbot_cleanup_hook.ps1"
# Скрипт для аутентификации
@"
param([string]`$Domain, [string]`$Token)
# Вызов функции добавления TXT записи
# Здесь должна быть логика работы с API reg.ru
"@ | Set-Content -Path $authHookScript
# Скрипт для очистки
@"
param([string]`$Domain, [string]`$Token)
# Вызов функции удаления TXT записи
"@ | Set-Content -Path $cleanupHookScript
Write-Log "Примечание: Для полной автоматизации используйте Linux версию скрипта" "WARNING"
Write-Log "На Windows рекомендуется использовать плагин certbot-dns-регru или выполнить вручную" "WARNING"
# Команда certbot (базовая, требует ручной DNS валидации)
$certbotArgs = @(
"certonly",
"--manual",
"--preferred-challenges", "dns",
"--email", $Script:Email,
"--agree-tos",
"--manual-public-ip-logging-ok"
) + $domainArgs
Write-Log "Запуск certbot..."
Write-Log "ВАЖНО: Certbot запросит вас добавить TXT записи в DNS" "WARNING"
Write-Log "Используйте API reg.ru или добавьте записи вручную через панель управления" "WARNING"
try {
& certbot @certbotArgs
Write-Log "Процесс получения сертификата завершен" "SUCCESS"
return $true
}
catch {
Write-Log "Ошибка при получении сертификата: $_" "ERROR"
return $false
}
}
function Update-Certificate {
Write-Log "=== Обновление SSL сертификата ===" "INFO"
try {
& certbot renew
Write-Log "Проверка обновления завершена" "SUCCESS"
return $true
}
catch {
Write-Log "Ошибка при обновлении: $_" "ERROR"
return $false
}
}
function Show-CertificateInfo {
param([string]$Domain)
$certPath = Join-Path $env:ProgramData "letsencrypt\live\$Domain\cert.pem"
if (-not (Test-Path $certPath)) {
Write-Log "Сертификат не найден" "WARNING"
return
}
Write-Log ("=" * 60)
Write-Log "ИНФОРМАЦИЯ О СЕРТИФИКАТЕ"
Write-Log ("=" * 60)
try {
$certInfo = & openssl x509 -in $certPath -text -noout
# Выводим основную информацию
$certInfo -split "`n" | Where-Object {
$_ -match "Subject:|Issuer:|Not Before|Not After|DNS:"
} | ForEach-Object {
Write-Log $_.Trim()
}
Write-Log ("=" * 60)
Write-Log "ПУТИ К ФАЙЛАМ СЕРТИФИКАТА:"
Write-Log " Сертификат: $certPath"
Write-Log " Приватный ключ: $(Join-Path $env:ProgramData "letsencrypt\live\$Domain\privkey.pem")"
Write-Log " Цепочка: $(Join-Path $env:ProgramData "letsencrypt\live\$Domain\chain.pem")"
Write-Log " Полная цепочка: $(Join-Path $env:ProgramData "letsencrypt\live\$Domain\fullchain.pem")"
Write-Log ("=" * 60)
}
catch {
Write-Log "Ошибка при чтении сертификата: $_" "ERROR"
}
}
# ============================================================================
# ОСНОВНАЯ ЛОГИКА
# ============================================================================
function Main {
Write-Log ("=" * 60)
Write-Log "СКРИПТ УПРАВЛЕНИЯ SSL СЕРТИФИКАТАМИ LET'S ENCRYPT"
Write-Log ("=" * 60)
# Проверка прав администратора
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($currentUser)
$isAdmin = $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
Write-Log "ПРЕДУПРЕЖДЕНИЕ: Скрипт запущен без прав администратора" "WARNING"
Write-Log "Некоторые операции могут потребовать повышенных прав" "WARNING"
}
# Загрузка конфигурации
if ($ConfigFile -and (Test-Path $ConfigFile)) {
Load-Configuration -ConfigPath $ConfigFile
}
# Проверка обязательных параметров
if (-not $Script:RegRuUsername -or -not $Script:RegRuPassword) {
Write-Log "ОШИБКА: Не указаны учетные данные reg.ru" "ERROR"
Write-Log "Укажите RegRuUsername и RegRuPassword или создайте файл конфигурации" "ERROR"
return
}
# Проверка Certbot
if (-not (Test-CertbotInstalled)) {
Write-Log "Установите Certbot и повторите попытку" "ERROR"
return
}
# Проверка срока действия сертификата
$daysLeft = Get-CertificateExpiry -Domain $Script:Domain
if ($null -eq $daysLeft) {
Write-Log "Сертификат не найден. Требуется создание нового." "INFO"
$success = Get-Certificate -Domain $Script:Domain -WildcardCert $Script:Wildcard
}
elseif ($daysLeft -lt 30) {
Write-Log "Сертификат истекает через $daysLeft дней. Требуется обновление!" "WARNING"
$success = Update-Certificate
}
else {
Write-Log "Сертификат действителен ($daysLeft дней)" "SUCCESS"
$success = $true
}
if ($success) {
Show-CertificateInfo -Domain $Script:Domain
}
Write-Log ("=" * 60)
Write-Log "Скрипт завершен"
Write-Log ("=" * 60)
}
# ============================================================================
# ТОЧКА ВХОДА
# ============================================================================
# Запуск основной функции
Main

831
letsencrypt_regru_api.py Normal file
View File

@@ -0,0 +1,831 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Скрипт для создания и обновления SSL сертификата Let's Encrypt
с использованием DNS-валидации через API reg.ru
Автор: GitHub Copilot
Дата: 27.10.2025
Описание:
Этот скрипт автоматизирует процесс получения и обновления SSL сертификатов
Let's Encrypt для доменов, зарегистрированных на reg.ru, используя DNS-01 challenge.
Скрипт напрямую работает с API reg.ru для управления DNS записями.
Требования:
- Python 3.6+
- requests
- certbot
- cryptography
Установка зависимостей:
pip install requests certbot cryptography
"""
import os
import sys
import json
import time
import logging
import argparse
import subprocess
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple
try:
import requests
except ImportError:
print("ОШИБКА: Необходимо установить модуль 'requests'")
print("Выполните: pip install requests")
sys.exit(1)
# ==============================================================================
# КОНФИГУРАЦИЯ
# ==============================================================================
# Настройки по умолчанию
DEFAULT_CONFIG = {
# Учетные данные API reg.ru
"regru_username": "your_username",
"regru_password": "your_password",
# Параметры домена
"domain": "dfv24.com",
"wildcard": True, # Создавать wildcard сертификат (*.domain.com)
# Email для уведомлений Let's Encrypt
"email": "admin@dfv24.com",
# Директории
"cert_dir": "/etc/letsencrypt/live",
"log_file": "/var/log/letsencrypt_regru.log",
# Параметры DNS
"dns_propagation_wait": 60, # Время ожидания распространения DNS (секунды)
"dns_check_attempts": 10, # Количество попыток проверки DNS
"dns_check_interval": 10, # Интервал между проверками DNS (секунды)
}
# API endpoints для reg.ru
REGRU_API_URL = "https://api.reg.ru/api/regru2"
# ==============================================================================
# НАСТРОЙКА ЛОГИРОВАНИЯ
# ==============================================================================
def setup_logging(log_file: str, verbose: bool = False) -> logging.Logger:
"""
Настройка системы логирования
Args:
log_file: Путь к файлу лога
verbose: Режим подробного вывода
Returns:
Logger объект
"""
log_level = logging.DEBUG if verbose else logging.INFO
# Создаем директорию для логов, если не существует
log_dir = os.path.dirname(log_file)
if log_dir and not os.path.exists(log_dir):
os.makedirs(log_dir, exist_ok=True)
# Настройка форматирования
formatter = logging.Formatter(
'%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# Создаем logger
logger = logging.getLogger('LetsEncrypt_RegRU')
logger.setLevel(log_level)
# Обработчик для файла
file_handler = logging.FileHandler(log_file, encoding='utf-8')
file_handler.setLevel(log_level)
file_handler.setFormatter(formatter)
# Обработчик для консоли
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(log_level)
console_handler.setFormatter(formatter)
# Добавляем обработчики
logger.addHandler(file_handler)
logger.addHandler(console_handler)
return logger
# ==============================================================================
# КЛАСС ДЛЯ РАБОТЫ С API REG.RU
# ==============================================================================
class RegRuAPI:
"""Класс для работы с API reg.ru"""
def __init__(self, username: str, password: str, logger: logging.Logger):
"""
Инициализация API клиента
Args:
username: Имя пользователя reg.ru
password: Пароль reg.ru
logger: Logger объект
"""
self.username = username
self.password = password
self.logger = logger
self.session = requests.Session()
def _make_request(self, method: str, params: Dict) -> Dict:
"""
Выполнение запроса к API reg.ru
Args:
method: Название метода API
params: Параметры запроса
Returns:
Ответ API в формате dict
"""
url = f"{REGRU_API_URL}/{method}"
# Добавляем учетные данные к параметрам
params.update({
"username": self.username,
"password": self.password,
"output_format": "json"
})
try:
self.logger.debug(f"Отправка запроса к API: {method}")
response = self.session.post(url, data=params)
response.raise_for_status()
result = response.json()
if result.get("result") == "success":
self.logger.debug(f"Запрос {method} выполнен успешно")
return result
else:
error_msg = result.get("error_text", "Неизвестная ошибка")
self.logger.error(f"Ошибка API: {error_msg}")
raise Exception(f"API Error: {error_msg}")
except requests.exceptions.RequestException as e:
self.logger.error(f"Ошибка HTTP запроса: {e}")
raise
def get_zone_records(self, domain: str) -> List[Dict]:
"""
Получение DNS записей домена
Args:
domain: Доменное имя
Returns:
Список DNS записей
"""
self.logger.info(f"Получение DNS записей для домена: {domain}")
params = {
"domain": domain,
}
result = self._make_request("zone/get_resource_records", params)
if "answer" in result and "records" in result["answer"]:
records = result["answer"]["records"]
self.logger.info(f"Получено {len(records)} DNS записей")
return records
else:
self.logger.warning("DNS записи не найдены")
return []
def add_txt_record(self, domain: str, subdomain: str, txt_value: str) -> bool:
"""
Добавление TXT записи для DNS валидации
Args:
domain: Основной домен
subdomain: Поддомен (например, _acme-challenge)
txt_value: Значение TXT записи
Returns:
True если успешно, False в противном случае
"""
self.logger.info(f"Добавление TXT записи: {subdomain}.{domain} = {txt_value}")
params = {
"domain": domain,
"subdomain": subdomain,
"text": txt_value,
"output_content_type": "plain"
}
try:
self._make_request("zone/add_txt", params)
self.logger.info("TXT запись успешно добавлена")
return True
except Exception as e:
self.logger.error(f"Не удалось добавить TXT запись: {e}")
return False
def remove_txt_record(self, domain: str, subdomain: str, txt_value: str) -> bool:
"""
Удаление TXT записи
Args:
domain: Основной домен
subdomain: Поддомен
txt_value: Значение TXT записи
Returns:
True если успешно, False в противном случае
"""
self.logger.info(f"Удаление TXT записи: {subdomain}.{domain}")
# Сначала получаем список всех записей
records = self.get_zone_records(domain)
# Ищем нужную TXT запись
record_id = None
for record in records:
if (record.get("rectype") == "TXT" and
record.get("subdomain") == subdomain and
record.get("text") == txt_value):
record_id = record.get("id")
break
if not record_id:
self.logger.warning("TXT запись для удаления не найдена")
return False
params = {
"domain": domain,
"record_id": record_id
}
try:
self._make_request("zone/remove_record", params)
self.logger.info("TXT запись успешно удалена")
return True
except Exception as e:
self.logger.error(f"Не удалось удалить TXT запись: {e}")
return False
# ==============================================================================
# КЛАСС ДЛЯ РАБОТЫ С CERTBOT
# ==============================================================================
class LetsEncryptManager:
"""Класс для управления сертификатами Let's Encrypt"""
def __init__(self, config: Dict, api: RegRuAPI, logger: logging.Logger):
"""
Инициализация менеджера сертификатов
Args:
config: Конфигурация
api: API клиент reg.ru
logger: Logger объект
"""
self.config = config
self.api = api
self.logger = logger
self.domain = config["domain"]
self.email = config["email"]
self.cert_dir = os.path.join(config["cert_dir"], self.domain)
def check_certbot_installed(self) -> bool:
"""
Проверка установки certbot
Returns:
True если certbot установлен
"""
try:
result = subprocess.run(
["certbot", "--version"],
capture_output=True,
text=True,
check=True
)
self.logger.debug(f"Certbot установлен: {result.stdout.strip()}")
return True
except (subprocess.CalledProcessError, FileNotFoundError):
self.logger.error("Certbot не установлен!")
return False
def check_certificate_expiry(self) -> Optional[int]:
"""
Проверка срока действия сертификата
Returns:
Количество дней до истечения или None если сертификат не найден
"""
cert_file = os.path.join(self.cert_dir, "cert.pem")
if not os.path.exists(cert_file):
self.logger.info("Сертификат не найден")
return None
try:
from cryptography import x509
from cryptography.hazmat.backends import default_backend
with open(cert_file, "rb") as f:
cert_data = f.read()
cert = x509.load_pem_x509_certificate(cert_data, default_backend())
expiry_date = cert.not_valid_after
days_left = (expiry_date - datetime.now()).days
self.logger.info(f"Сертификат истекает: {expiry_date.strftime('%Y-%m-%d')}")
self.logger.info(f"Осталось дней: {days_left}")
return days_left
except Exception as e:
self.logger.error(f"Ошибка при проверке сертификата: {e}")
return None
def dns_challenge_hook(self, validation_domain: str, validation_token: str) -> bool:
"""
Обработчик DNS challenge - добавление TXT записи
Args:
validation_domain: Домен для валидации (_acme-challenge.domain.com)
validation_token: Токен валидации
Returns:
True если успешно
"""
self.logger.info("=== DNS Challenge: Добавление TXT записи ===")
# Извлекаем поддомен из validation_domain
# Формат: _acme-challenge.domain.com или _acme-challenge
parts = validation_domain.replace(f".{self.domain}", "").split(".")
subdomain = parts[0] if parts else "_acme-challenge"
# Добавляем TXT запись
success = self.api.add_txt_record(self.domain, subdomain, validation_token)
if success:
# Ждем распространения DNS
wait_time = self.config.get("dns_propagation_wait", 60)
self.logger.info(f"Ожидание распространения DNS ({wait_time} секунд)...")
time.sleep(wait_time)
# Проверяем DNS запись
if self.verify_dns_record(subdomain, validation_token):
self.logger.info("DNS валидация готова")
return True
else:
self.logger.warning("DNS запись не распространилась вовремя, но продолжаем...")
return True
return False
def dns_cleanup_hook(self, validation_domain: str, validation_token: str) -> bool:
"""
Обработчик очистки DNS challenge - удаление TXT записи
Args:
validation_domain: Домен валидации
validation_token: Токен валидации
Returns:
True если успешно
"""
self.logger.info("=== DNS Challenge: Удаление TXT записи ===")
parts = validation_domain.replace(f".{self.domain}", "").split(".")
subdomain = parts[0] if parts else "_acme-challenge"
return self.api.remove_txt_record(self.domain, subdomain, validation_token)
def verify_dns_record(self, subdomain: str, expected_value: str) -> bool:
"""
Проверка наличия DNS записи
Args:
subdomain: Поддомен
expected_value: Ожидаемое значение TXT записи
Returns:
True если запись найдена
"""
import socket
full_domain = f"{subdomain}.{self.domain}"
attempts = self.config.get("dns_check_attempts", 10)
interval = self.config.get("dns_check_interval", 10)
self.logger.info(f"Проверка DNS записи для {full_domain}")
for attempt in range(attempts):
try:
# Используем nslookup или dig через subprocess
result = subprocess.run(
["nslookup", "-type=TXT", full_domain],
capture_output=True,
text=True,
timeout=10
)
if expected_value in result.stdout:
self.logger.info(f"DNS запись найдена (попытка {attempt + 1})")
return True
except Exception as e:
self.logger.debug(f"Попытка {attempt + 1}: DNS запись не найдена - {e}")
if attempt < attempts - 1:
time.sleep(interval)
self.logger.warning("DNS запись не найдена после всех попыток")
return False
def obtain_certificate(self) -> bool:
"""
Получение нового сертификата
Returns:
True если успешно
"""
self.logger.info("=== Запрос нового SSL сертификата ===")
# Формируем список доменов
domains = [self.domain]
if self.config.get("wildcard", False):
domains.append(f"*.{self.domain}")
domain_args = []
for d in domains:
domain_args.extend(["-d", d])
# Команда certbot
cmd = [
"certbot", "certonly",
"--manual",
"--preferred-challenges", "dns",
"--manual-auth-hook", f"{sys.executable} {os.path.abspath(__file__)} --auth-hook",
"--manual-cleanup-hook", f"{sys.executable} {os.path.abspath(__file__)} --cleanup-hook",
"--email", self.email,
"--agree-tos",
"--non-interactive",
"--expand",
] + domain_args
self.logger.info(f"Выполнение команды: {' '.join(cmd)}")
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=True
)
self.logger.info("Сертификат успешно получен!")
self.logger.debug(result.stdout)
return True
except subprocess.CalledProcessError as e:
self.logger.error(f"Ошибка при получении сертификата: {e}")
self.logger.error(e.stderr)
return False
def renew_certificate(self) -> bool:
"""
Обновление существующего сертификата
Returns:
True если успешно
"""
self.logger.info("=== Обновление SSL сертификата ===")
cmd = [
"certbot", "renew",
"--manual",
"--manual-auth-hook", f"{sys.executable} {os.path.abspath(__file__)} --auth-hook",
"--manual-cleanup-hook", f"{sys.executable} {os.path.abspath(__file__)} --cleanup-hook",
"--non-interactive",
]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=True
)
self.logger.info("Проверка обновления завершена")
self.logger.debug(result.stdout)
return True
except subprocess.CalledProcessError as e:
self.logger.error(f"Ошибка при обновлении: {e}")
self.logger.error(e.stderr)
return False
def display_certificate_info(self):
"""Вывод информации о сертификате"""
cert_file = os.path.join(self.cert_dir, "cert.pem")
if not os.path.exists(cert_file):
self.logger.warning("Сертификат не найден")
return
self.logger.info("=" * 60)
self.logger.info("ИНФОРМАЦИЯ О СЕРТИФИКАТЕ")
self.logger.info("=" * 60)
try:
result = subprocess.run(
["openssl", "x509", "-in", cert_file, "-text", "-noout"],
capture_output=True,
text=True,
check=True
)
# Выводим только основную информацию
for line in result.stdout.split("\n"):
if any(keyword in line for keyword in ["Subject:", "Issuer:", "Not Before", "Not After", "DNS:"]):
self.logger.info(line.strip())
self.logger.info("=" * 60)
self.logger.info("ПУТИ К ФАЙЛАМ СЕРТИФИКАТА:")
self.logger.info(f" Сертификат: {self.cert_dir}/cert.pem")
self.logger.info(f" Приватный ключ: {self.cert_dir}/privkey.pem")
self.logger.info(f" Цепочка: {self.cert_dir}/chain.pem")
self.logger.info(f" Полная цепочка: {self.cert_dir}/fullchain.pem")
self.logger.info("=" * 60)
except Exception as e:
self.logger.error(f"Ошибка при чтении сертификата: {e}")
# ==============================================================================
# ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
# ==============================================================================
def reload_webserver(logger: logging.Logger):
"""
Перезагрузка веб-сервера
Args:
logger: Logger объект
"""
logger.info("Перезагрузка веб-сервера...")
# Проверяем какие сервисы активны
services = ["nginx", "apache2", "httpd"]
for service in services:
try:
# Проверяем статус
result = subprocess.run(
["systemctl", "is-active", service],
capture_output=True,
text=True
)
if result.stdout.strip() == "active":
# Перезагружаем
subprocess.run(
["systemctl", "reload", service],
check=True
)
logger.info(f"Сервис {service} перезагружен")
return
except Exception as e:
logger.debug(f"Сервис {service} не активен или ошибка: {e}")
logger.warning("Активный веб-сервер не найден")
def load_config(config_file: Optional[str] = None) -> Dict:
"""
Загрузка конфигурации из файла или использование значений по умолчанию
Args:
config_file: Путь к файлу конфигурации (JSON)
Returns:
Словарь с конфигурацией
"""
config = DEFAULT_CONFIG.copy()
if config_file and os.path.exists(config_file):
try:
with open(config_file, 'r', encoding='utf-8') as f:
user_config = json.load(f)
config.update(user_config)
except Exception as e:
print(f"ОШИБКА: Не удалось загрузить конфигурацию из {config_file}: {e}")
sys.exit(1)
return config
def create_sample_config(output_file: str):
"""
Создание примера файла конфигурации
Args:
output_file: Путь к выходному файлу
"""
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(DEFAULT_CONFIG, f, indent=4, ensure_ascii=False)
print(f"Пример конфигурации создан: {output_file}")
print("Отредактируйте файл и укажите ваши учетные данные")
# ==============================================================================
# ОСНОВНАЯ ФУНКЦИЯ
# ==============================================================================
def main():
"""Основная функция скрипта"""
# Парсинг аргументов командной строки
parser = argparse.ArgumentParser(
description="Автоматическое управление SSL сертификатами Let's Encrypt через API reg.ru"
)
parser.add_argument(
"-c", "--config",
help="Путь к файлу конфигурации (JSON)",
default=None
)
parser.add_argument(
"--create-config",
help="Создать пример файла конфигурации",
metavar="FILE"
)
parser.add_argument(
"--obtain",
help="Получить новый сертификат",
action="store_true"
)
parser.add_argument(
"--renew",
help="Обновить существующий сертификат",
action="store_true"
)
parser.add_argument(
"--check",
help="Проверить срок действия сертификата",
action="store_true"
)
parser.add_argument(
"--auth-hook",
help="Внутренний хук для DNS аутентификации (используется certbot)",
action="store_true"
)
parser.add_argument(
"--cleanup-hook",
help="Внутренний хук для очистки DNS (используется certbot)",
action="store_true"
)
parser.add_argument(
"-v", "--verbose",
help="Подробный вывод",
action="store_true"
)
args = parser.parse_args()
# Создание примера конфигурации
if args.create_config:
create_sample_config(args.create_config)
return 0
# Загрузка конфигурации
config = load_config(args.config)
# Настройка логирования
logger = setup_logging(config["log_file"], args.verbose)
# Обработка хуков для certbot
if args.auth_hook:
# Certbot передает домен и токен через переменные окружения
domain = os.environ.get("CERTBOT_DOMAIN")
token = os.environ.get("CERTBOT_VALIDATION")
if domain and token:
api = RegRuAPI(config["regru_username"], config["regru_password"], logger)
manager = LetsEncryptManager(config, api, logger)
success = manager.dns_challenge_hook(domain, token)
return 0 if success else 1
else:
logger.error("CERTBOT_DOMAIN или CERTBOT_VALIDATION не установлены")
return 1
if args.cleanup_hook:
domain = os.environ.get("CERTBOT_DOMAIN")
token = os.environ.get("CERTBOT_VALIDATION")
if domain and token:
api = RegRuAPI(config["regru_username"], config["regru_password"], logger)
manager = LetsEncryptManager(config, api, logger)
success = manager.dns_cleanup_hook(domain, token)
return 0 if success else 1
else:
logger.error("CERTBOT_DOMAIN или CERTBOT_VALIDATION не установлены")
return 1
# Проверка прав root
if os.geteuid() != 0:
logger.error("Скрипт должен быть запущен от имени root (sudo)")
return 1
# Инициализация API и менеджера
api = RegRuAPI(config["regru_username"], config["regru_password"], logger)
manager = LetsEncryptManager(config, api, logger)
# Проверка certbot
if not manager.check_certbot_installed():
logger.error("Установите certbot: apt-get install certbot")
return 1
logger.info("=" * 60)
logger.info("СКРИПТ УПРАВЛЕНИЯ SSL СЕРТИФИКАТАМИ LET'S ENCRYPT")
logger.info("=" * 60)
# Выполнение действий
if args.check:
# Только проверка срока действия
days_left = manager.check_certificate_expiry()
if days_left is None:
logger.info("Сертификат не найден. Требуется создание нового.")
return 2
elif days_left < 30:
logger.warning(f"Сертификат истекает через {days_left} дней. Требуется обновление!")
return 1
else:
logger.info(f"Сертификат действителен ({days_left} дней)")
return 0
elif args.obtain:
# Принудительное получение нового сертификата
success = manager.obtain_certificate()
if success:
manager.display_certificate_info()
reload_webserver(logger)
logger.info("Новый сертификат успешно создан")
return 0
else:
logger.error("Не удалось получить сертификат")
return 1
elif args.renew:
# Обновление существующего сертификата
success = manager.renew_certificate()
if success:
manager.display_certificate_info()
reload_webserver(logger)
logger.info("Сертификат успешно обновлен")
return 0
else:
logger.error("Не удалось обновить сертификат")
return 1
else:
# Автоматический режим: проверка и обновление при необходимости
days_left = manager.check_certificate_expiry()
if days_left is None:
# Сертификат не существует
logger.info("Сертификат не найден. Создание нового...")
success = manager.obtain_certificate()
elif days_left < 30:
# Сертификат скоро истекает
logger.info(f"Сертификат истекает через {days_left} дней. Обновление...")
success = manager.renew_certificate()
else:
# Сертификат действителен
logger.info(f"Сертификат действителен ({days_left} дней). Обновление не требуется.")
manager.display_certificate_info()
return 0
if success:
manager.display_certificate_info()
reload_webserver(logger)
logger.info("Операция завершена успешно")
return 0
else:
logger.error("Операция завершилась с ошибкой")
return 1
if __name__ == "__main__":
sys.exit(main())

290
letsencrypt_regru_dns.sh Normal file
View File

@@ -0,0 +1,290 @@
#!/bin/bash
###############################################################################
# Скрипт для создания и обновления SSL сертификата Let's Encrypt
# с использованием DNS-валидации через API reg.ru
#
# Автор: GitHub Copilot
# Дата: 27.10.2025
# Описание: Автоматизация получения wildcard сертификата через Certbot
# с использованием DNS-01 challenge и API reg.ru
###############################################################################
# ==============================================================================
# КОНФИГУРАЦИЯ
# ==============================================================================
# Учетные данные API reg.ru (получить на https://www.reg.ru/user/account/)
REGRU_USERNAME="your_username" # Имя пользователя reg.ru
REGRU_PASSWORD="your_password" # Пароль от аккаунта reg.ru
# Параметры домена и сертификата
DOMAIN="dfv24.com" # Основной домен
WILDCARD_DOMAIN="*.dfv24.com" # Wildcard домен
EMAIL="admin@dfv24.com" # Email для уведомлений Let's Encrypt
# Директории для хранения сертификатов
CERT_DIR="/etc/letsencrypt/live/$DOMAIN"
CREDENTIALS_DIR="/etc/letsencrypt/credentials"
CREDENTIALS_FILE="$CREDENTIALS_DIR/regru.ini"
# Логирование
LOG_FILE="/var/log/letsencrypt_regru.log"
TIMESTAMP=$(date '+%d.%m.%Y %H:%M:%S')
# ==============================================================================
# ФУНКЦИИ
# ==============================================================================
# Логирование сообщений
log() {
echo "[$TIMESTAMP] $1" | tee -a "$LOG_FILE"
}
# Проверка установки необходимых пакетов
check_dependencies() {
log "Проверка зависимостей..."
# Проверка certbot
if ! command -v certbot &> /dev/null; then
log "ОШИБКА: certbot не установлен. Установите: apt-get install certbot"
exit 1
fi
# Проверка curl
if ! command -v curl &> /dev/null; then
log "ОШИБКА: curl не установлен. Установите: apt-get install curl"
exit 1
fi
# Проверка jq для обработки JSON
if ! command -v jq &> /dev/null; then
log "ОШИБКА: jq не установлен. Установите: apt-get install jq"
exit 1
fi
log "Все зависимости установлены"
}
# Создание директории для credentials
setup_credentials_dir() {
log "Настройка директории для учетных данных..."
if [ ! -d "$CREDENTIALS_DIR" ]; then
mkdir -p "$CREDENTIALS_DIR"
chmod 700 "$CREDENTIALS_DIR"
fi
}
# Создание файла с учетными данными для certbot-dns-regru
create_credentials_file() {
log "Создание файла с учетными данными reg.ru..."
cat > "$CREDENTIALS_FILE" <<EOF
# Учетные данные API reg.ru для DNS-валидации
dns_regru_username = $REGRU_USERNAME
dns_regru_password = $REGRU_PASSWORD
EOF
chmod 600 "$CREDENTIALS_FILE"
log "Файл учетных данных создан: $CREDENTIALS_FILE"
}
# Установка плагина certbot-dns-regru (если еще не установлен)
install_certbot_plugin() {
log "Проверка плагина certbot-dns-regru..."
if ! pip3 list | grep -q certbot-dns-regru; then
log "Установка certbot-dns-regru..."
pip3 install certbot-dns-regru
if [ $? -eq 0 ]; then
log "Плагин certbot-dns-regru успешно установлен"
else
log "ОШИБКА: Не удалось установить certbot-dns-regru"
exit 1
fi
else
log "Плагин certbot-dns-regru уже установлен"
fi
}
# Получение нового сертификата
obtain_certificate() {
log "Запрос нового SSL сертификата для домена: $DOMAIN и $WILDCARD_DOMAIN"
certbot certonly \
--dns-regru \
--dns-regru-credentials "$CREDENTIALS_FILE" \
--dns-regru-propagation-seconds 60 \
-d "$DOMAIN" \
-d "$WILDCARD_DOMAIN" \
--email "$EMAIL" \
--agree-tos \
--non-interactive \
--preferred-challenges dns-01
if [ $? -eq 0 ]; then
log "Сертификат успешно получен!"
log "Путь к сертификатам: $CERT_DIR"
return 0
else
log "ОШИБКА: Не удалось получить сертификат"
return 1
fi
}
# Обновление существующего сертификата
renew_certificate() {
log "Проверка и обновление существующих сертификатов..."
certbot renew \
--dns-regru \
--dns-regru-credentials "$CREDENTIALS_FILE" \
--dns-regru-propagation-seconds 60 \
--non-interactive
if [ $? -eq 0 ]; then
log "Проверка обновления завершена успешно"
return 0
else
log "ОШИБКА: Проблема при обновлении сертификата"
return 1
fi
}
# Проверка срока действия сертификата
check_certificate_expiry() {
log "Проверка срока действия сертификата..."
if [ -f "$CERT_DIR/cert.pem" ]; then
EXPIRY_DATE=$(openssl x509 -enddate -noout -in "$CERT_DIR/cert.pem" | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
CURRENT_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $CURRENT_EPOCH) / 86400 ))
log "Сертификат истекает: $EXPIRY_DATE (осталось дней: $DAYS_LEFT)"
if [ $DAYS_LEFT -lt 30 ]; then
log "ВНИМАНИЕ: Сертификат истекает менее чем через 30 дней. Требуется обновление!"
return 1
else
log "Сертификат действителен"
return 0
fi
else
log "Сертификат не найден. Требуется создание нового сертификата."
return 2
fi
}
# Перезагрузка веб-сервера (Nginx/Apache)
reload_webserver() {
log "Перезагрузка веб-сервера..."
# Определяем, какой веб-сервер используется
if systemctl is-active --quiet nginx; then
systemctl reload nginx
log "Nginx перезагружен"
elif systemctl is-active --quiet apache2; then
systemctl reload apache2
log "Apache перезагружен"
elif systemctl is-active --quiet httpd; then
systemctl reload httpd
log "Apache (httpd) перезагружен"
else
log "ВНИМАНИЕ: Веб-сервер не найден или не активен. Пропускаем перезагрузку."
fi
}
# Отправка уведомления (опционально)
send_notification() {
local MESSAGE=$1
log "Уведомление: $MESSAGE"
# Здесь можно добавить отправку email, Telegram, Slack и т.д.
# Пример: echo "$MESSAGE" | mail -s "SSL Certificate Update" admin@example.com
}
# Вывод информации о сертификате
display_certificate_info() {
if [ -f "$CERT_DIR/cert.pem" ]; then
log "=========================================="
log "Информация о сертификате:"
log "=========================================="
openssl x509 -in "$CERT_DIR/cert.pem" -text -noout | grep -E "(Subject:|Issuer:|Not Before|Not After|DNS:)" | tee -a "$LOG_FILE"
log "=========================================="
log "Пути к файлам сертификата:"
log " Сертификат: $CERT_DIR/cert.pem"
log " Приватный ключ: $CERT_DIR/privkey.pem"
log " Цепочка: $CERT_DIR/chain.pem"
log " Полная цепочка: $CERT_DIR/fullchain.pem"
log "=========================================="
fi
}
# ==============================================================================
# ОСНОВНАЯ ЛОГИКА
# ==============================================================================
main() {
log "=========================================="
log "Запуск скрипта управления SSL сертификатом"
log "=========================================="
# Проверка, что скрипт запущен от root
if [ "$EUID" -ne 0 ]; then
log "ОШИБКА: Скрипт должен быть запущен от имени root (sudo)"
exit 1
fi
# Проверка зависимостей
check_dependencies
# Настройка директории и файла учетных данных
setup_credentials_dir
create_credentials_file
# Установка плагина certbot-dns-regru
install_certbot_plugin
# Проверка существования сертификата
check_certificate_expiry
CERT_STATUS=$?
if [ $CERT_STATUS -eq 2 ]; then
# Сертификат не существует - создаем новый
obtain_certificate
if [ $? -eq 0 ]; then
display_certificate_info
reload_webserver
send_notification "Новый SSL сертификат успешно создан для $DOMAIN"
else
send_notification "ОШИБКА: Не удалось создать SSL сертификат для $DOMAIN"
exit 1
fi
elif [ $CERT_STATUS -eq 1 ]; then
# Сертификат скоро истекает - обновляем
renew_certificate
if [ $? -eq 0 ]; then
display_certificate_info
reload_webserver
send_notification "SSL сертификат успешно обновлен для $DOMAIN"
else
send_notification "ОШИБКА: Не удалось обновить SSL сертификат для $DOMAIN"
exit 1
fi
else
# Сертификат действителен - только проверяем обновление
log "Сертификат действителен. Выполняем проверку на наличие обновлений..."
renew_certificate
display_certificate_info
fi
log "=========================================="
log "Скрипт завершен успешно"
log "=========================================="
}
# Запуск основной функции
main

View File

@@ -0,0 +1,79 @@
# Подробная инструкция по настройке Nginx Proxy Manager с одним глобальным SSL сертификатом для всех доменов dfv24.com
## Предпосылки
- Установлен и запущен [Nginx Proxy Manager](http://192.168.10.14:81/)
- Основной домен: dfv24.com
- Хостинг и DNS записи домена находятся на reg.ru
- Нужно использовать один SSL сертификат (например, wildcard) для всех поддоменов dfv24.com
---
## Шаг 1. Покупка и получение SSL Wildcard сертификата для dfv24.com
1. На reg.ru или любом другом удостоверяющем центре (CA) закажите wildcard сертификат на домен вида `*.dfv24.com`.
2. Получите файлы сертификата:
- Главный сертификат (CRT)
- Промежуточные сертификаты (CA Bundle)
- Приватный ключ (KEY)
---
## Шаг 2. Импорт вашего SSL сертификата в Nginx Proxy Manager
1. Авторизуйтесь в Nginx Proxy Manager на http://192.168.10.14:81/
2. Перейдите в раздел **SSL Certificates** → кнопку **Add SSL Certificate**
3. Выберите **Custom** (пользовательский сертификат)
4. В поля вставьте:
- **Certificate** — основной CRT + CA Bundle (если CA Bundle раздельно, склейте в один файл или вставляйте последовательно)
- **Key** — содержимое приватного ключа
- Имя сертификата задайте, например, `dfv24_wildcard`
5. Сохраните
---
## Шаг 3. Настройка прокси-хостов с использованием глобального сертификата
1. Перейдите в **Proxy Hosts****Add Proxy Host**
2. Заполните поля:
- **Domain Names**: Например, `sub1.dfv24.com` (для первого поддомена)
- **Scheme**: http или https, в зависимости от бекенда
- **Forward Hostname / IP**: IP или DNS адрес вашего внутреннего сервиса
- **Forward Port**: порт сервиса (например, 80 или 443)
3. Включите **SSL** → Отметьте **Use a shared SSL certificate** (если такая опция доступна) или выберите ранее импортированный сертификат из списка
4. Активируйте: **Block Common Exploits**, **Websockets Support**, выставьте Redirect HTTP to HTTPS, если требуется
5. Сохраните прокси-хост
6. Повторите для всех поддоменов, указывая нужные домены и выбирая тот же wildcard SSL сертификат
---
## Шаг 4. Настройка DNS записей на reg.ru
1. Войдите в панель управления доменом на reg.ru
2. Создайте или отредактируйте DNS записи типа A:
- `dfv24.com` → IP вашего Nginx Proxy Manager (например, 192.168.10.14)
- `*.dfv24.com` → тот же IP или конкретные поддомены, если есть специальные
3. Сохраните изменения
4. Дождитесь обновления DNS (от нескольких минут до 24 часов)
---
## Шаг 5. Тест и проверка работы
1. В браузере откройте любой из поддоменов `https://sub1.dfv24.com`
2. Сертификат должен быть валидным, выданным на wildcard `*.dfv24.com`
3. Проверьте работу прокси и корректность подстановки сертификата
4. При необходимости проверьте логи Nginx Proxy Manager и исправьте ошибки
---
## Дополнительно
- Если в Nginx Proxy Manager нет GUI опции выбора общего сертификата, можно вручную сконфигурировать конфиги через директорий `/data/nginx/proxy_host` и прописать SSL сертификат для всех хостов.
- При обновлении сертификата — повторно импортировать его в Nginx Proxy Manager.
- Можно использовать LetsEncrypt для автоматического получения wildcard сертификата с помощью DNS-валидации (если поддерживается вашим DNS-провайдером).
---
# Итог
Используйте один wildcard сертификат для всех поддоменов, импортируйте его как пользовательский сертификат в Nginx Proxy Manager, при создании прокси-хостов выбирайте его в настройках SSL. Управляйте DNS записями на reg.ru, направляя domен на IP Nginx Proxy Manager.
Это позволит юридически использовать единый сертификат для всех сервисов с различными поддоменами под вашим доменом dfv24.com.