HTTPS для приложения Node.js: сертификат ACME через acme.sh

Печать
  • 0

Приложение 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.jsNode.js сам обслуживает HTTPS
Кто занимает порты 80 и 443Nginx; приложение слушает локальный порт, например 3000Процесс Node.js
СертификатCertbot с --nginxacme.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.

Полезные статьи

Помог ли вам данный ответ?

Нужна помощь? Позвоните нам +373 795-800-80