HTTPS для приложения Node.js: сертификат ACME через acme.sh
ПечатьПриложение Node.js на VPS можно перевести на HTTPS двумя способами: поставить перед ним Nginx и доверить сертификат Certbot (обычный вариант для рабочих сайтов) или позволить самому процессу Node.js обслуживать HTTPS с сертификатом от acme.sh. Оба способа работают с подпиской Sectigo ACME и продлеваются автоматически. Ниже объясняется, когда какой выбрать, а второй разобран полностью.
На тарифах Web Hosting SSD в TopHost приложения Node.js создаются в cPanel через Setup Node.js App и работают за веб-сервером хостинга, поэтому используют сертификат домена от AutoSSL или установленный по статье acme.sh на хостинге cPanel. Там эта инструкция не нужна.
Какой способ выбрать
| Nginx перед Node.js | Node.js сам обслуживает HTTPS | |
|---|---|---|
| Кто занимает порты 80 и 443 | Nginx; приложение слушает локальный порт, например 3000 | Процесс Node.js |
| Сертификат | Certbot с --nginx | acme.sh, файлы читает приложение |
| После продления | Nginx перечитывает конфигурацию, приложение не перезапускается | Приложение перезапускается через PM2 |
| Подходит для | Рабочих сайтов, нескольких приложений на одном сервере | Одного небольшого приложения без другого веб-сервера |
Способ 1: Nginx как обратный прокси
Пусть приложение слушает только локальный адрес, например app.listen(3000, '127.0.0.1'). Затем создайте в Nginx блок server для домена:
server {
listen 80;
server_name example.com www.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Перезагрузите Nginx и получите сертификат через Certbot в автоматическом режиме, как в статье Certbot и Sectigo ACME для Apache и Nginx. Certbot допишет HTTPS-часть в этот блок и будет сам продлевать сертификат.
Способ 2: Node.js со своим HTTPS-сервером
В примере приложение Express лежит в /home/appuser/myapp и запускается через PM2 обычным пользователем (не root) appuser. acme.sh ставится под тем же пользователем, чтобы его задание продления перезапускало нужный процесс PM2.
Шаг 1. Разрешите Node.js занимать порты 80 и 443
Порты ниже 1024 доступны только root. Вместо запуска приложения от root выдайте исполняемому файлу Node.js одно это право:
sudo setcap 'cap_net_bind_service=+ep' "$(readlink -f "$(which node)")"
Повторяйте команду после каждого обновления Node.js: новый исполняемый файл это право не сохраняет. Откройте порты 80 и 443 в файрволе сервера.
Шаг 2. Отдавайте папку проверки по HTTP
Для первого сертификата приложение должно отвечать на порту 80 и отдавать файлы из public/.well-known/acme-challenge/. Express скрывает папки, имя которых начинается с точки, поэтому эту папку нужно подключить явно:
const express = require('express');
const http = require('http');
const path = require('path');
const webroot = path.join(__dirname, 'public');
const app = express();
app.use('/.well-known/acme-challenge',
express.static(path.join(webroot, '.well-known', 'acme-challenge')));
app.use(express.static(webroot));
http.createServer(app).listen(80);
Шаг 3. Запустите приложение через PM2
sudo npm install -g pm2
pm2 start server.js --name myapp
pm2 startup systemd
pm2 save
pm2 startup выводит команду, начинающуюся с sudo; скопируйте и выполните её, чтобы приложение поднималось после перезагрузки сервера. pm2 save запоминает текущий список процессов.
Шаг 4. Установите acme.sh и зарегистрируйтесь
От имени appuser:
curl https://get.acme.sh | sh -s email=admin@example.com
source ~/.bashrc
acme.sh --register-account --server https://acme.sectigo.com/v2/DV \
--eab-kid YOUR_EAB_MAC_ID --eab-hmac-key YOUR_EAB_MAC_KEY
Значения берутся в личном кабинете, в услуге Sectigo ACME Certificate-as-a-Service.
Шаг 5. Выпустите сертификат и передайте его приложению
acme.sh --issue --server https://acme.sectigo.com/v2/DV \
-d example.com -d www.example.com \
-w /home/appuser/myapp/public
mkdir -p /home/appuser/myapp/cert
chmod 700 /home/appuser/myapp/cert
acme.sh --install-cert -d example.com \
--key-file /home/appuser/myapp/cert/privkey.pem \
--fullchain-file /home/appuser/myapp/cert/fullchain.pem \
--reloadcmd "pm2 restart myapp"
Папка после -w должна совпадать с той, что отдаёт приложение на шаге 2. acme.sh записывает закрытый ключ с ограниченными правами; держите папку cert вне public и вне Git-репозитория.
Шаг 6. Переведите приложение на HTTPS
Замените server.js версией, которая оставляет проверку на HTTP, перенаправляет остальные запросы на HTTPS и загружает сертификат:
const express = require('express');
const fs = require('fs');
const http = require('http');
const https = require('https');
const path = require('path');
const webroot = path.join(__dirname, 'public');
const certDir = path.join(__dirname, 'cert');
// само приложение, работает по HTTPS
const app = express();
app.use(express.static(webroot));
app.get('/', function (req, res) {
res.send('Hello over HTTPS');
});
// порт 80: проверка ACME остаётся на HTTP, остальное перенаправляется
const plain = express();
plain.use('/.well-known/acme-challenge',
express.static(path.join(webroot, '.well-known', 'acme-challenge')));
plain.use(function (req, res) {
res.redirect(301, 'https://' + req.hostname + req.originalUrl);
});
http.createServer(plain).listen(80);
https.createServer({
key: fs.readFileSync(path.join(certDir, 'privkey.pem')),
cert: fs.readFileSync(path.join(certDir, 'fullchain.pem'))
}, app).listen(443);
Примените изменения командой pm2 restart myapp. Node.js читает сертификат только при запуске, поэтому --reloadcmd из шага 5 перезапускает приложение после каждого продления.
Шаг 7. Проверьте HTTPS и продление
- Откройте
https://example.comи посмотрите сведения о сертификате, нажав на замок. Откройте иhttp://example.com: должно сработать перенаправление на HTTPS. - От имени
appuserвыполнитеcrontab -lи найдите строку сacme.sh --cron. - Один раз выполните
acme.sh --renew -d example.com --force, чтобы проверить продление и автоматический перезапуск, затем посмотритеpm2 status.
Решение проблем
- Error: listen EACCES на порту 80 или 443. Нет права из шага 1; так бывает после обновления Node.js или если Node.js установлен через менеджер версий в другой путь.
- Error: listen EADDRINUSE. Порт уже занят другой программой, часто Nginx или Apache. Остановите её или выберите способ 1.
- Проверка возвращает 404. Папка из
-wне та, что отдаётся по адресу/.well-known/acme-challenge/, или перенаправление на HTTPS перехватывает запрос проверки, когда сертификата ещё нет. - После продления приложение не перезапустилось. acme.sh и PM2 работают под разными пользователями. Установите оба под одним пользователем или укажите правильную команду в
--reloadcmd. - Domain not found при запуске
--renewили--install-cert. Добавьте--ecc: современные версии acme.sh по умолчанию выпускают сертификаты ECC.
Если разобраться не получается, откройте тикет и приложите команду, её вывод и журнал PM2 (pm2 logs myapp), без EAB MAC Key.