OAuth 2.0 Flow для MCP Inspector и совместимых клиентов / OAuth 2.0 Flow for MCP Inspector and compatible clients
MCP OAuth Flow
OAuth 2.0 Flow для MCP Inspector и совместимых клиентов / OAuth 2.0 Flow for MCP Inspector and compatible clients
- Authorization Code + PKCE флоу (RFC 6749 + RFC 7636)
- Форма логин/пароль / Login form (username/password)
- JWT
access_token+refresh_tokenс ротацией / JWTaccess_token+refresh_tokenwith rotation /.well-known/oauth-authorization-server(RFC 8414)- Отзыв токенов (RFC 7009) / Token revocation (RFC 7009)
- Хранение пользователей с bcrypt-хешами / User storage with bcrypt hashes

Архитектура / Architecture
Браузер / MCP Inspector
│
▼
nginx (443 HTTPS)
│
├──/oauth/──► oauth_server.py :9002 (логин, выдача JWT / login, JWT issuance)
├──/.well-known/──► oauth_server.py :9002 (метаданные OAuth / OAuth metadata)
└──/mcp──► mcp_server.py :6339 (MCP, проверяет JWT / MCP, verifies JWT)
Пользователь в MCP Inspector вводит URL сервера -> Inspector сам обнаруживает OAuth, открывает браузер с формой логина -> после входа токен подставляется автоматически.
User enters server URL in MCP Inspector -> Inspector auto-discovers OAuth, opens browser with login form -> after sign-in the token is injected automatically.
Ограничения (важно знать до деплоя) / Limitations (know before deploy)
-
Нет инвалидации всех сессий пользователя разом - только поштучный revoke по конкретному токену. Если нужно разлогинить пользователя на всех устройствах, придётся крутить
OAUTH_SECRET_KEY(все JWT станут невалидными). -
/oauth/revokeи/oauth/token/infoпо умолчанию открыты всем В проде обязательно задаватьOAUTH_RESOURCE_SERVER_SECRET- см. раздел «Обязательные переменные для прода». -
CORS по умолчанию разрешает только
http://localhost:6274иhttp://127.0.0.1:6274(MCP Inspector). Для своего origin задаватьOAUTH_CORS_ORIGINSявно. -
CLI-команды (
--add-user,--list-users,--remove-user) ходят в ту же SQLite-БД, что и сервер. При активном сервисе возможна задержка до 30с из-заdatabase is locked(см._DB_CONNECT_TIMEOUTв коде). Останавливать сервис на время правки. -
Нет refresh-token rotation detection (TODO в коде). Утёкший refresh-токен нельзя отозвать до истечения
expires_at, кроме как явнымPOST /oauth/revoke. -
bcrypt обрезает пароли длиннее 72 байт (TODO в коде). Для длинных passphrase-ов - argon2/scrypt.
-
No bulk session invalidation — only per-token revoke. To log a user out across all devices, rotate
OAUTH_SECRET_KEY(all JWTs become invalid). -
/oauth/revokeand/oauth/token/infoare open by default — in prod always setOAUTH_RESOURCE_SERVER_SECRET(see "Required prod env vars" section). -
CORS allows only
http://localhost:6274andhttp://127.0.0.1:6274by default (MCP Inspector). For a custom origin, setOAUTH_CORS_ORIGINSexplicitly. -
CLI commands (
--add-user,--list-users,--remove-user) hit the same SQLite DB as the server. With the service running you may wait up to 30s due todatabase is locked(see_DB_CONNECT_TIMEOUTin code). Stop the service for the duration of edits. -
No refresh-token rotation detection (TODO in code). A leaked refresh token cannot be revoked before
expires_at— only via an explicitPOST /oauth/revoke. -
bcrypt truncates passwords longer than 72 bytes (TODO in code). For long passphrases — use argon2/scrypt.
Тестовый локальный запуск / Local test run
sudo apt install python3-tk
cd ~/McpOAuthFlow
python3 -m venv v_env
source v_env/bin/activate
pip install -r requirements.txt
cd ~/McpOAuthFlow && source v_env/bin/activate && python3 oauth_server.py --add-user admin admin
cd ~/McpOAuthFlow && source v_env/bin/activate && OAUTH_HOST="http://localhost" OAUTH_PORT="9002" OAUTH_SECRET_KEY="de01969527df1b0ef32e58eccb078819851b2a1a4a34478f38794ad2d72bce97" python3 oauth_server.py
cd ~/McpOAuthFlow && source v_env/bin/activate && OAUTH_SECRET_KEY="de01969527df1b0ef32e58eccb078819851b2a1a4a34478f38794ad2d72bce97" OAUTH_ISSUER="http://localhost:9002/oauth" python3 mcp_server.py
npx @modelcontextprotocol/inspector@0.21.2
http://127.0.0.1:6339/mcp
Генерируем секретный ключ / Generate the secret key
Один ключ используется в обоих сервисах (oauth_server и mcp_server) - они должны совпадать.
One key is used in both services (oauth_server and mcp_server) — they must match.
python3 -c "import secrets; print(secrets.token_hex(32))"
# Сохранить вывод, далее это OAUTH_SECRET_KEY
# Save the output; this becomes OAUTH_SECRET_KEY
# Пример: a3f8d2c1e4b7...
# Example: a3f8d2c1e4b7...
Сгенерировать заодно секрет для resource-server-эндпоинтов для прода: Generate a secret for resource-server endpoints for prod as well:
python3 -c "import secrets; print(secrets.token_hex(32))"
# Сохранить, далее это OAUTH_RESOURCE_SERVER_SECRET
# Save it; this becomes OAUTH_RESOURCE_SERVER_SECRET
Установка / Install
sudo apt install python3-tk
mkdir -p /opt/mcp
chown -R www-data:www-data /opt/mcp
chmod 755 /opt/mcp
cp ~/McpOAuthFlow/requirements.txt /opt/mcp/requirements.txt
cp ~/McpOAuthFlow/oauth_server.py /opt/mcp/oauth_server.py
cp ~/McpOAuthFlow/mcp_server.py /opt/mcp/mcp_server.py
cd /opt/mcp
python3 -m venv v_env
source v_env/bin/activate
pip install -r requirements.txt
Обязательные переменные для прода / Required prod env vars
OAuth-сервер (mcp-oauth.service) / OAuth server
| Переменная / Variable | Обязательная / Required | Назначение / Purpose |
|---|---|---|
| OAUTH_SECRET_KEY | да / yes | Секрет подписи JWT (≥ 32 символов) / JWT signing secret (≥ 32 chars) |
| OAUTH_HOST | да / yes | Публичный URL, например https://mcp.example.com. Без /oauth на конце - oauth_server допишет / Public URL, e.g. https://mcp.example.com. No trailing /oauth — oauth_server appends it |
| OAUTH_PORT | нет (9002) / no (9002) | Внутренний порт, на котором слушает oauth_server / Internal port oauth_server listens on |
| OAUTH_BIND_HOST | нет (127.0.0.1) / no (127.0.0.1) | На каком интерфейсе слушать. Для Docker - 0.0.0.0 / Interface to bind. For Docker — 0.0.0.0 |
| MCP_SERVER_URL | нет (http://localhost:6339/mcp) / no | URL MCP-сервера, прописывается в метаданные OAuth / MCP server URL, written into OAuth metadata |
| OAUTH_CORS_ORIGINS | да для прода / yes for prod | Свой origin через запятую, например https://mcp.example.com. Пустая строка "" - CORS выключен / Your origin, comma-separated, e.g. https://mcp.example.com. Empty string "" — CORS off |
| OAUTH_RESOURCE_SERVER_SECRET | да для прода / yes for prod | Bearer-токен для /oauth/revoke и /oauth/token/info. Если не задан, то эти эндпоинты открыты / Bearer token for /oauth/revoke and /oauth/token/info. If unset, those endpoints are open |
| OAUTH_TRUSTED_PROXIES | да для прода за nginx / yes for prod behind nginx | IP nginx (например 127.0.0.1), чтобы rate-limit видел реальный IP клиента из X-Forwarded-For / nginx IP (e.g. 127.0.0.1) so rate-limit sees the real client IP from X-Forwarded-For |
| OAUTH_DB | нет / no | Путь к SQLite. Для прод лучше вынести в /var/lib/mcp/oauth_users.db / SQLite path. For prod, prefer /var/lib/mcp/oauth_users.db |
MCP-сервер (mcp-server.service) / MCP server
| Переменная / Variable | Обязательная / Required | Назначение / Purpose |
|---|---|---|
| OAUTH_SECRET_KEY | да / yes | Тот же, что в oauth_server / Same as in oauth_server |
| OAUTH_ISSUER | да / yes | Полный issuer с /oauth, например https://mcp.example.com:9002/oauth, https://mcp.example.com/oauth / Full issuer with /oauth, e.g. https://mcp.example.com:9002/oauth, https://mcp.example.com/oauth |
systemd-сервис для OAuth-сервера / systemd unit for OAuth server
cat > /etc/systemd/system/mcp-oauth.service << 'EOF'
[Unit]
Description=MCP OAuth 2.0 Authorization Server
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/mcp
Environment="OAUTH_SECRET_KEY=ВСТАВЬТЕ_СВОЙ_СЕКРЕТ"
Environment="OAUTH_HOST=https://mcp.example.com"
Environment="OAUTH_PORT=9002"
Environment="OAUTH_BIND_HOST=127.0.0.1"
Environment="MCP_SERVER_URL=http://localhost:6339/mcp"
# Для прод-деплоя раскомментировать и подставить свои значения:
# Environment="OAUTH_CORS_ORIGINS=https://mcp.example.com"
# Environment="OAUTH_RESOURCE_SERVER_SECRET=ВСТАВИТЬ_СВОЙ_СЕКРЕТ"
# Environment="OAUTH_TRUSTED_PROXIES=127.0.0.1"
# Если выносите БД из /opt/mcp:
# Environment="OAUTH_DB=/var/lib/mcp/oauth_users.db"
ExecStart=/opt/mcp/v_env/bin/python /opt/mcp/oauth_server.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemd-сервис для MCP-сервера / systemd unit for MCP server
cat > /etc/systemd/system/mcp-server.service << 'EOF'
[Unit]
Description=MCP Programming Search Server
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/mcp
Environment="OAUTH_SECRET_KEY=ВСТАВЬТЕ_ТЕМ_ЖЕ_ЧТО_В_MCP-OAUTH"
Environment="OAUTH_ISSUER=https://mcp.example.com/oauth"
ExecStart=/opt/mcp/v_env/bin/python /opt/mcp/mcp_server.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
OAUTH_SECRET_KEYдолжен быть одинаковым в обоих сервисах.OAUTH_SECRET_KEYmust be identical in both services.
Настраиваем nginx / Configure nginx
# Заменяем домен в конфиге nginx.conf
# Replace the domain in nginx.conf
cp ~/McpOAuthFlow/nginx.conf /etc/nginx/sites-available/mcp
ln -sf /etc/nginx/sites-available/mcp /etc/nginx/sites-enabled/
# Проверяем конфиг / Validate the config
nginx -t
# Применяем конфиг (пока без SSL - только HTTP для certbot)
# Apply the config (HTTP-only for now, for certbot)
systemctl reload nginx
Получаем SSL-сертификат Let's Encrypt / Get a Let's Encrypt SSL cert
certbot --nginx -d ДОМЕН
# certbot сам отредактирует конфиг nginx и добавит ssl_certificate строки
# certbot will edit the nginx config and add ssl_certificate lines
# После этого перезагрузите конфиг ещё раз:
# Then reload the config again:
systemctl reload nginx
Альтернативный шаблон nginx с автопродлением: https://github.com/Arlandaren/nginx-template
Добавляем пользователей / Add users
CLI работает напрямую с SQLite-БД oauth_server'а. При активном сервисе команды могут ждать до 30 секунд из-за блокировки. The CLI talks directly to oauth_server's SQLite DB. With the service running, commands may wait up to 30 seconds due to the lock.
# Добавить пользователя / Add a user
cd /opt/mcp && source v_env/bin/activate && python3 oauth_server.py --add-user alice СЕКРЕТНЫЙ_ПАРОЛЬ
cd /opt/mcp && source v_env/bin/activate && python3 oauth_server.py --add-user bob ДРУГОЙ_СЕКРЕТНЫЙ_ПАРОЛЬ
# Посмотреть список / List users
cd /opt/mcp && source v_env/bin/activate && python3 oauth_server.py --list-users
# Удалить пользователя / Remove a user
cd /opt/mcp && source v_env/bin/activate && python3 oauth_server.py --remove-user alice
Как удалить всех пользователей / How to wipe all users
# ВАЖНО: сначала остановите сервис, иначе WAL пересоздаст файл
# и текущие активные сессии потеряются, но без чистого завершения.
# IMPORTANT: stop the service first, otherwise WAL will recreate the file
# and active sessions will be lost — without a clean shutdown.
systemctl stop mcp-oauth
rm -f /opt/mcp/oauth_users.db /opt/mcp/oauth_users.db-wal /opt/mcp/oauth_users.db-shm
systemctl start mcp-oauth
Запускаем сервисы / Start the services
systemctl daemon-reload
systemctl enable mcp-oauth mcp-server
systemctl start mcp-oauth mcp-server
# Проверяем статус / Check status
systemctl status mcp-oauth
systemctl status mcp-server
Подключение через MCP Inspector / Connect via MCP Inspector
- Открыть MCP Inspector (
npx @modelcontextprotocol/inspector). - Transport Type: Streamable HTTP.
- Server URL:
https://ДОМЕН/mcp. - Нажать Connect / Click Connect.
- Inspector обнаружит OAuth и откроет браузер с формой логина. Inspector will discover OAuth and open a browser with the login form.
- Ввести логин/пароль -> токен подставится автоматически. Enter username/password -> the token is injected automatically.
Подключение через Claude Desktop / Cursor / другой AI-клиент / Connect via Claude Desktop / Cursor / other AI client
{
"mcpServers": {
"programming-search": {
"type": "streamable-http",
"url": "https://ДОМЕН/mcp",
"oauth": {
"issuer": "https://ДОМЕН/oauth"
}
}
}
}
Формат
oauth.issuer- конвенция MCP, не этого сервера. Поддержка зависит от конкретного клиента; для Inspector работает, для Claude Desktop / Cursor может потребовать адаптации. Theoauth.issuerformat is an MCP convention, not this server's. Support depends on the specific client; works in Inspector, may need adaptation for Claude Desktop / Cursor.
Структура файлов на сервере / Server file layout
/opt/mcp/
├── mcp_server.py - MCP-сервер (порт 6339) / MCP server (port 6339)
├── oauth_server.py - OAuth-сервер (порт 9002) / OAuth server (port 9002)
├── v_env/ - Python venv
├── requirements.txt
└── oauth_users.db - SQLite с пользователями (создаётся автоматически; если вынесли - путь см. в OAUTH_DB)
/ SQLite with users (auto-created; if relocated, see OAUTH_DB)
-/etc/systemd/system/mcp-oauth.service
/etc/systemd/system/mcp-server.service
Бэкап и восстановление / Backup and restore
oauth_users.db содержит:
oauth_users.db contains:
-
bcrypt-хеши паролей пользователей;
-
активные refresh-токены (пока не истекли);
-
failed_logins(используется для lockout-а - можно чистить). -
bcrypt password hashes;
-
active refresh tokens (until expiry);
-
failed_logins(used for lockout — safe to clear).
Рекомендуемая схема / Recommended scheme:
# Ежедневный бэкап / Daily backup
sqlite3 /opt/mcp/oauth_users.db ".backup /var/backups/mcp/oauth_$(date +%F).db"
# Восстановление / Restore
systemctl stop mcp-oauth
cp /var/backups/mcp/oauth_2025-01-15.db /opt/mcp/oauth_users.db
chown www-data:www-data /opt/mcp/oauth_users.db
systemctl start mcp-oauth
Бэкап через .backup безопасен при работающем сервисе (атомарный snapshot через
WAL), но восстановление только после stop.
.backup is safe while the service is running (atomic WAL snapshot), but restore
only after stop.
Ротация секретов / Secret rotation
OAUTH_SECRET_KEY (главный JWT-секрет / main JWT secret)
Если ключ утёк, нужно одновременно обновить его в обоих сервисах и
перезапустить их. Все текущие access_token и refresh_token станут
невалидными и пользователям придётся залогиниться заново.
If the key leaks, update it in both services simultaneously and restart
them. All current access_token and refresh_token become invalid and users
must sign in again.
# Сгенерировать новый ключ / Generate a new key
NEW_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")
# Обновить в обоих юнитах / Update in both units
sed -i "s/OAUTH_SECRET_KEY=.*/OAUTH_SECRET_KEY=$NEW_KEY/" /etc/systemd/system/mcp-oauth.service
sed -i "s/OAUTH_SECRET_KEY=.*/OAUTH_SECRET_KEY=$NEW_KEY/" /etc/systemd/system/mcp-server.service
# Перезапустить / Restart
systemctl daemon-reload
systemctl restart mcp-oauth mcp-server
OAUTH_RESOURCE_SERVER_SECRET
# Сгенерировать / Generate
NEW_RS=$(python3 -c "import secrets; print(secrets.token_hex(32))")
# Обновить в oauth-юните / Update in the oauth unit
sed -i "s/OAUTH_RESOURCE_SERVER_SECRET=.*/OAUTH_RESOURCE_SERVER_SECRET=$NEW_RS/" /etc/systemd/system/mcp-oauth.service
# Обновить у всех клиентов, которые ходят в /oauth/revoke и /oauth/token/info
# (это делает админ, клиенты не входят в этот репозиторий)
# Update on all clients that hit /oauth/revoke and /oauth/token/info
# (admin's job — clients are not part of this repo)
# Перезапустить / Restart
systemctl daemon-reload
systemctl restart mcp-oauth
Обновление / Upgrade
# Бэкап БД / DB backup
systemctl stop mcp-oauth
cp /opt/mcp/oauth_users.db /var/backups/mcp/oauth_$(date +%F)_pre-upgrade.db
# Копируем новые файлы / Copy new files
scp mcp_server.py user@your-server:/opt/mcp/
scp oauth_server.py user@your-server:/opt/mcp/
# Перезапускаем / Restart
systemctl start mcp-oauth
systemctl restart mcp-server
# Проверяем логи / Check logs
journalctl -u mcp-oauth -f
# При первом старте после обновления могут появиться строки вида
# "duplicate column name: scope" - это нормальная миграция БД (ALTER TABLE
# IF NOT EXISTS через try/except). Должны пройти за 1-2 секунды и больше
# не появляться.
# On the first start after an upgrade you may see lines like
# "duplicate column name: scope" — that's a normal DB migration (ALTER TABLE
# IF NOT EXISTS via try/except). They should clear in 1-2 seconds and not
# reappear.
Если после обновления в логах ошибки - откатываться: If logs show errors after upgrade — roll back:
systemctl stop mcp-oauth mcp-server
cp /var/backups/mcp/oauth_YYYY-MM-DD_pre-upgrade.db /opt/mcp/oauth_users.db
scp ~/McpOAuthFlow/oauth_server.py user@your-server:/opt/mcp/ # предыдущая версия / previous version
systemctl start mcp-oauth mcp-server
Приложение. Сертификат на IP (без домена) - 6-дневные сертификаты / Appendix. IP cert (no domain) — 6-day certs
Этот раздел нужен только если нет домена и нужен HTTPS прямо на IP-адрес. Если домен есть - не делать. This section is needed only if you have no domain and need HTTPS directly on an IP address. If you have a domain — skip it.
Let's Encrypt с января 2026 года выдаёт сертификаты на IP-адреса, но только
с профилем shortlived (срок жизни - 160 часов, ~6 дней). Поэтому обязательно
нужно настроить автоматическое обновление через cron.
Since January 2026 Let's Encrypt issues IP-address certs only under the
shortlived profile (lifetime 160h, ~6 days). So you must configure auto-renewal via cron.
Вариант А - Certbot ≥ 5.3 (флаг --ip-address) / Option A
# Убедитесь что версия 5.3+ / Make sure it's 5.3+
certbot --version
# Обновить если нужно: / Upgrade if needed:
pip3 install --upgrade certbot
# Создаём директорию для webroot-challenge (nginx должен её отдавать)
# Create webroot-challenge dir (nginx must serve it)
mkdir -p /var/www/certbot
# Убедитесь что nginx проксирует /.well-known/acme-challenge/ из этой папки.
# В nginx.conf для порта 80 уже есть нужный блок:
# location /.well-known/acme-challenge/ { root /var/www/certbot; }
# Make sure nginx serves /.well-known/acme-challenge/ from this folder.
# The port-80 nginx.conf already has the right block.
# Тест на staging (не выдаёт реальный сертификат, но проверяет всё):
# Staging test (no real cert, but verifies everything):
certbot certonly --staging \
--preferred-profile shortlived \
--webroot --webroot-path /var/www/certbot \
--ip-address ВАШ_IP_АДРЕС
# Если тест прошёл - получаем боевой сертификат:
# If the test passed — get the production cert:
certbot certonly \
--preferred-profile shortlived \
--webroot --webroot-path /var/www/certbot \
--ip-address ВАШ_IP_АДРЕС
# Сертификат появится в:
# /etc/letsencrypt/live/ВАШ_IP_АДРЕС/fullchain.pem
# /etc/letsencrypt/live/ВАШ_IP_АДРЕС/privkey.pem
Certbot 5.3 не умеет автоматически прописывать IP-сертификаты в nginx. Нужно прописать пути вручную в
nginx_mcp.conf:ssl_certificate /etc/letsencrypt/live/ВАШ_IP/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/ВАШ_IP/privkey.pem;Certbot 5.3 can't auto-wire IP certs into nginx. You must set the paths manually in
nginx_mcp.conf:ssl_certificate /etc/letsencrypt/live/ВАШ_IP/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/ВАШ_IP/privkey.pem;
Вариант Б - lego (проще для IP, не требует webroot) / Option B
lego - альтернативный ACME-клиент, уже полностью поддерживает IP + shortlived.
Плюс: запускает встроенный HTTP-сервер сам, не нужен webroot.
Минус: нужно на время обновления останавливать nginx (освобождать порт 80).
lego is an alternative ACME client with full IP + shortlived support.
Plus: runs its own built-in HTTP server, no webroot needed.
Minus: you must stop nginx for the renewal (freeing port 80).
# Устанавливаем lego / Install lego
curl -Lo /tmp/lego.tar.gz \
https://github.com/go-acme/lego/releases/latest/download/lego_v4.23.1_linux_amd64.tar.gz
tar -xz -C /usr/local/bin -f /tmp/lego.tar.gz lego
# Получаем первый сертификат (nginx нужно остановить на ~30 секунд):
# Get the first cert (stop nginx for ~30s):
systemctl stop nginx
lego --accept-tos \
--email admin@example.com \
--domains ВАШ_IP_АДРЕС \
--http --disable-cn \
run --profile shortlived
systemctl start nginx
# Сертификат появится в:
# /root/.lego/certificates/ВАШ_IP_АДРЕС.crt
# /root/.lego/certificates/ВАШ_IP_АДРЕС.key
Прописываем в nginx / Wire it into nginx:
ssl_certificate /root/.lego/certificates/ВАШ_IP_АДРЕС.crt;
ssl_certificate_key /root/.lego/certificates/ВАШ_IP_АДРЕС.key;
systemctl reload nginx
Автообновление через cron (обязательно - сертификат живёт 6 дней) / Auto-renewal via cron (required — cert lives 6 days)
Сертификат нужно обновлять каждые 4–5 дней (до истечения 6-дневного срока). Renew the cert every 4–5 days (before the 6-day lifetime expires).
Для Certbot (вариант А) - создаём deploy-hook, который перезагружает nginx после обновления, и добавляем в cron: For Certbot (option A) — create a deploy-hook that reloads nginx after renewal, and add it to cron:
# Deploy-hook - перезагружает nginx после успешного обновления
# Deploy-hook — reloads nginx after a successful renewal
cat > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh << 'EOF'
#!/bin/bash
systemctl reload nginx
EOF
chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
# Добавляем в cron - запуск каждые 4 дня в 3:00 (время сервера)
# (certbot сам проверяет нужно ли обновлять; webroot не требует остановки nginx)
# Add to cron — every 4 days at 03:00 (server time)
# (certbot itself checks if renewal is needed; webroot doesn't require stopping nginx)
crontab -e
# Добавить строку: / Add this line:
0 3 */4 * * certbot renew --quiet
Для lego (вариант Б) - nginx нужно останавливать на время обновления: For lego (option B) — nginx must be stopped during renewal:
# Скрипт обновления / Renewal script
cat > /opt/mcp/renew-cert.sh << 'EOF'
#!/bin/bash
set -e
systemctl stop nginx
lego --accept-tos \
--email admin@example.com \
--domains ВАШ_IP_АДРЕС \
--http --disable-cn \
renew --profile shortlived
systemctl start nginx
systemctl reload nginx
EOF
chmod +x /opt/mcp/renew-cert.sh
# Добавляем в cron - каждые 4 дня в 3:00 (время сервера)
# Add to cron — every 4 days at 03:00 (server time)
crontab -e
# Добавить строку: / Add this line:
0 3 */4 * * /opt/mcp/renew-cert.sh >> /var/log/lego-renew.log 2>&1
Сколько nginx будет недоступен? Обычно 10–30 секунд - только на время ACME-challenge. How long is nginx down? Usually 10–30 seconds — only for the ACME challenge.