Подготовьте небольшую контролируемую среду
Эта процедура рассчитана на систему Linux с systemd, учётной записью администратора, уже установленной средой выполнения Node.js 24 LTS, curl и пакетной системной службой Caddy. Сначала подтвердите установленные версии и пути пакетов; страница релизов Node определяет поддерживаемые линии релизов. Установка и подготовка провайдером — отдельные задачи. Используйте машину, которую контролируете, и держите наготове проверенный сеанс SSH и путь восстановления. Имена first-api и /opt/first-api должны быть неиспользованными перед созданием этого примера.
Для HTTPS также нужен домен, который вы контролируете, корректные A/AAAA-записи и разрешение открывать веб-трафик. api.example.com ниже — зарезервированный пример: замените его своим именем хоста. Это API намеренно не содержит базы данных, аутентификации или данных клиентов. Оно демонстрирует повторяемый процесс, а не готовый продукт или протестированное развёртывание OffVPS.
Проверьте среду выполнения и создайте один релиз
command -v node &&
readlink -f "$(command -v node)" &&
node --version
Остальная часть примера предполагает, что проверенный общий исполняемый файл — это /usr/bin/node. Если у вас другой, замените этот путь в каждой проверке и в ExecStart. Среда выполнения в приватном домашнем каталоге вашего пользователя входа не становится автоматически доступной системной службе. Создайте служебную учётную запись и принадлежащий root каталог релиза, останавливаясь при обнаружении неожиданной существующей учётной записи или пути.
sudo useradd --system --user-group --home-dir /opt/first-api \
--shell /usr/sbin/nologin first-api &&
sudo install -d -o root -g root -m 0755 /opt/first-api/releases/001
С помощью редактора с административным доступом сохраните следующее как /opt/first-api/releases/001/server.mjs, принадлежащее root и читаемое служебным пользователем. Релиз содержит только этот файл; здесь нет зависимостей пакетов или секретов.
import http from 'node:http';
const port = Number(process.env.PORT || 3000);
if (!Number.isInteger(port) || port < 1024 || port > 65535) {
throw new Error('PORT must be an integer from 1024 to 65535');
}
const server = http.createServer((req, res) => {
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.setHeader('Cache-Control', 'no-store');
if (req.method !== 'GET') {
res.writeHead(405, { Allow: 'GET' });
res.end(JSON.stringify({ error: 'method_not_allowed' }));
return;
}
if (req.url === '/healthz') {
res.writeHead(200);
res.end(JSON.stringify({ status: 'ok', release: '001' }));
} else if (req.url === '/api/message') {
res.writeHead(200);
res.end(JSON.stringify({ message: 'A small app, running clearly.' }));
} else {
res.writeHead(404);
res.end(JSON.stringify({ error: 'not_found' }));
}
});
server.requestTimeout = 10000;
server.headersTimeout = 10000;
server.keepAliveTimeout = 5000;
server.listen(port, '127.0.0.1');
process.on('SIGTERM', () => {
server.close(() => process.exit(0));
setTimeout(() => process.exit(1), 10000).unref();
});
Явный адрес loopback удерживает слушателя API на самом сервере. Caddy будет его публичной точкой входа. HTTP API Node документирует обработку запросов, тайм-ауты и завершение работы сервера. Проверьте файл от имени учётной записи, которая фактически будет его выполнять:
sudo -u first-api /usr/bin/node --check /opt/first-api/releases/001/server.mjs
Дайте процессу определение службы
Сохраните /etc/systemd/system/first-api.service с этим содержимым:
[Unit]
Description=First API learning release
After=network.target
StartLimitIntervalSec=60
StartLimitBurst=5
[Service]
Type=simple
User=first-api
Group=first-api
WorkingDirectory=/opt/first-api/releases/001
ExecStart=/usr/bin/node /opt/first-api/releases/001/server.mjs
Environment=NODE_ENV=production
Environment=PORT=3000
Restart=on-failure
RestartSec=5
TimeoutStopSec=15
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
[Install]
WantedBy=multi-user.target
Модуль использует одного выделенного пользователя, явный исполняемый файл и версионированный рабочий каталог. Автоматическое восстановление ограничено по частоте; намеренная остановка службы не запускает Restart=on-failure. См. systemd.service. Ограничения файловой системы подходят для этого API только для чтения; приложению, которое записывает данные, нужно намеренно ограниченное записываемое хранилище. Секретам не место в этих строках окружения. См. настройки выполнения systemd.
sudo systemd-analyze verify /etc/systemd/system/first-api.service &&
sudo systemctl daemon-reload &&
sudo systemctl start first-api.service &&
sudo systemctl status first-api.service --no-pager &&
curl --fail --show-error http://127.0.0.1:3000/healthz
The && защитные механизмы останавливают вставленную последовательность при сбое команды. Устраните предупреждения проверки перед запуском и не переходите к следующему блоку после неудачной проверки. Проверка модуля может выявить проблемы синтаксиса и исполняемого файла, но успешная проверка не доказывает работу приложения. Ожидаемый ответ проверки здоровья — {"status":"ok","release":"001"}. Если он не проходит, изучите sudo journalctl -u first-api.service -n 50 --no-pager прежде чем многократно перезапускать.
Добавьте маршрут HTTPS после локальной проверки
Сделайте резервную копию существующей конфигурации Caddy под неиспользуемым именем файла. Добавьте этот блок в /etc/caddy/Caddyfile не заменяя несвязанные сайты:
api.example.com {
reverse_proxy 127.0.0.1:3000
}
Для стандартного потока с публичным доменом имя хоста должно разрешаться в сервер, порты 80/443 должны достигать Caddy, а хранилище сертификатов Caddy должно оставаться записываемым и постоянным. Проверьте каждый опубликованный маршрут A/AAAA. Оставьте доступ SSH нетронутым и держите порт 3000 приватным. Эти требования взяты из Автоматический HTTPS в Caddy; синтаксис upstream задокументирован в reverse_proxy.
sudo caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile &&
sudo systemctl reload caddy.service
Перезагружайте только после успешной проверки; caddy validate проверяет адаптированную конфигурацию. Рабочий процесс пакетной службы описан в руководстве по службе Caddy. С отдельного клиента запросите https://api.example.com/healthz используя ваше реальное имя хоста. Ожидайте доверенное TLS-соединение и тот же ответ релиза без обхода проверок сертификата. Затем проверьте, что /api/message возвращает своё сообщение, а неизвестный путь возвращает 404.
Сохраните известный релиз и безопасную точку остановки
После успешной работы обеих проверок включите API для будущих загрузок с помощью sudo systemctl enable first-api.service. Запишите версию среды выполнения, исходный файл, модуль и конфигурацию Caddy. Для следующего релиза создайте новый нумерованный каталог, проверьте его синтаксис от имени служебного пользователя, обновите оба пути модуля, перезагрузите systemd и перезапустите API. Сохраняйте предыдущий каталог, пока новый релиз не будет принят.
Чтобы остановить этот пример, используйте sudo systemctl stop first-api.service. Caddy сообщит об отказе upstream, пока этот маршрут остаётся настроенным; при выводе из эксплуатации удалите только этот маршрут и проверьте/перезагрузите Caddy. Откатите неудачный релиз кода, выбрав предыдущие пути модуля и повторив проверки. Более поздняя миграция базы данных требует собственного плана восстановления. Продолжите с путь запроса от DNS к приложению или выяснение, почему приложение остановилось.
Использованная документация
Основные ссылки для этой страницы. Проверьте документацию для версии, установленной в вашей среде.