Prepara un entorno pequeño y controlado
Este procedimiento apunta a un sistema Linux con systemd, una cuenta de administrador, un runtime Node.js 24 LTS ya instalado, curl y el servicio de sistema empaquetado de Caddy. Confirma primero las versiones instaladas y las rutas de los paquetes; La página de releases de Node identifica las líneas de release compatibles. La instalación y el aprovisionamiento del proveedor son tareas separadas. Usa una máquina que controles y mantén disponible una sesión SSH probada y una ruta de recuperación. Los nombres first-api y /opt/first-api deben estar sin usar antes de crear este ejemplo.
Para HTTPS también necesitas un dominio que controles, registros A/AAAA correctos y permiso para exponer tráfico web. api.example.com a continuación es un ejemplo reservado: reemplázalo con tu propio hostname. Esta API deliberadamente no contiene base de datos, autenticación ni datos de clientes. Demuestra un proceso repetible, no un producto completo ni un despliegue OffVPS probado.
Verifica el runtime y crea un release
command -v node &&
readlink -f "$(command -v node)" &&
node --version
El resto del ejemplo asume que el ejecutable compartido verificado es /usr/bin/node. Si el tuyo difiere, reemplaza esa ruta en cada comprobación y en ExecStart. Un runtime dentro del home privado de tu usuario de inicio de sesión no está disponible automáticamente para un servicio de sistema. Crea la cuenta de servicio y un directorio de release propiedad de root, deteniéndote si se encuentra una cuenta o ruta existente inesperada.
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
Usando tu editor con acceso administrativo, guarda lo siguiente como /opt/first-api/releases/001/server.mjs, propiedad de root y legible por el usuario del servicio. El release contiene solo este archivo; no hay dependencias de paquetes ni secretos.
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();
});
La dirección de loopback explícita mantiene el listener de la API en el propio servidor. Caddy será su punto de entrada público. La API HTTP de Node documenta el manejo de solicitudes, los timeouts y el apagado del servidor. Comprueba el archivo como la cuenta que realmente lo ejecutará:
sudo -u first-api /usr/bin/node --check /opt/first-api/releases/001/server.mjs
Dale al proceso una definición de servicio
Guarda /etc/systemd/system/first-api.service con este contenido:
[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
La unidad usa un usuario dedicado, un ejecutable explícito y un directorio de trabajo versionado. La recuperación automática tiene límite de frecuencia; una parada intencional del servicio no dispara Restart=on-failure. Consulta systemd.service. Las restricciones del sistema de archivos encajan con esta API de solo lectura; una aplicación que escribe datos necesita almacenamiento escribible con alcance deliberado. Los secretos no pertenecen a estas líneas de entorno. Consulta ajustes de ejecución de 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
El && las protecciones detienen la secuencia pegada cuando un comando falla. Resuelve las advertencias de validación antes de iniciar, y no continúes al siguiente bloque tras una comprobación fallida. Verificación de la unidad puede detectar problemas de sintaxis y de ejecutable, pero una comprobación exitosa no es prueba de una aplicación en funcionamiento. La respuesta de salud esperada es {"status":"ok","release":"001"}. Si falla, inspecciona sudo journalctl -u first-api.service -n 50 --no-pager antes de reiniciar repetidamente.
Añade la ruta HTTPS después de la comprobación local
Haz una copia de seguridad de la configuración existente de Caddy con un nombre de archivo sin usar. Añade este bloque a /etc/caddy/Caddyfile sin reemplazar sitios no relacionados:
api.example.com {
reverse_proxy 127.0.0.1:3000
}
Para el flujo estándar de dominio público, el hostname debe resolverse al servidor, los puertos 80/443 deben llegar a Caddy, y el almacenamiento de certificados de Caddy debe permanecer escribible y persistente. Comprueba cada ruta A/AAAA publicada. Mantén intacto el acceso SSH y mantén privado el puerto 3000. Estos requisitos provienen de HTTPS automático de Caddy; la sintaxis del upstream está documentada en reverse_proxy.
sudo caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile &&
sudo systemctl reload caddy.service
Recarga solo tras una validación exitosa; caddy validate comprueba la configuración adaptada. El flujo de trabajo del servicio empaquetado se describe en la guía de servicio de Caddy. Desde un cliente separado, solicita https://api.example.com/healthz usando tu hostname real. Espera una conexión TLS de confianza y la misma respuesta del release, sin omitir las comprobaciones de certificado. Luego verifica que /api/message devuelve su mensaje y una ruta desconocida devuelve 404.
Conserva un release conocido y un punto de parada seguro
Después de que ambas comprobaciones funcionen, habilita la API para futuros arranques con sudo systemctl enable first-api.service. Registra la versión del runtime, el archivo fuente, la unidad y la configuración de Caddy. Para el siguiente release, crea un nuevo directorio numerado, comprueba su sintaxis como el usuario del servicio, actualiza ambas rutas de unidad, recarga systemd y reinicia la API. Mantén el directorio anterior hasta que se acepte el nuevo release.
Para detener este ejemplo, usa sudo systemctl stop first-api.service. Caddy reportará un fallo de upstream mientras esa ruta siga configurada; elimina solo esta ruta y valida/recarga Caddy al retirarla. Haz rollback de un release de código fallido seleccionando las rutas de unidad anteriores y repitiendo las comprobaciones. Una migración posterior de base de datos necesita su propio plan de recuperación. Continúa con la ruta de solicitud de DNS a aplicación o encontrar por qué se detuvo una app.
Documentación utilizada
Referencias principales para esta página. Consulta la documentación de la versión instalada en tu propio entorno.