원샷: nginx + Node API + HTTPS (systemd)

복사-붙여넣기 한 번으로 Node(systemd) + nginx(/api 프록시) + Let’s Encrypt HTTPS까지 구성

분야: DevOps/인프라 nginxnodejssystemdhttpssslletsencryptcertbot

처음부터 흐름대로 이해하고 싶다면: nginx로 Node 서버 배포하기 (systemd + HTTPS)

원샷 스크립트

아래 스크립트는 “샘플 Node 서버 생성 → systemd 등록 → nginx 설정 → certbot 발급/리다이렉트 → 갱신 테스트”까지 한 번에 진행합니다.

export DOMAIN="example.com"
export EMAIL="you@example.com"
export APP_NAME="myapp"
export APP_PORT="3000"

sudo bash -euo pipefail <<BASH
set -euo pipefail

: "\${DOMAIN:?DOMAIN is required}"
: "\${EMAIL:?EMAIL is required}"
: "\${APP_NAME:?APP_NAME is required}"
: "\${APP_PORT:?APP_PORT is required}"

apt-get update -y
apt-get install -y nginx certbot python3-certbot-nginx nodejs

id -u "\${APP_NAME}" >/dev/null 2>&1 || useradd --system --create-home --shell /usr/sbin/nologin "\${APP_NAME}"

mkdir -p "/srv/\${APP_NAME}/current"
chown -R "\${APP_NAME}:\${APP_NAME}" "/srv/\${APP_NAME}"

cat >"/srv/\${APP_NAME}/current/server.js" <<'JS'
const http = require("http");
const port = Number(process.env.PORT || 3000);

const server = http.createServer((req, res) => {
  if (req.url === "/health") {
    res.writeHead(200, { "content-type": "text/plain; charset=utf-8" });
    res.end("ok");
    return;
  }
  if (req.url === "/hello") {
    res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
    res.end(JSON.stringify({ ok: true, message: "hello" }));
    return;
  }
  res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
  res.end("not found");
});

server.listen(port, "127.0.0.1", () => {
  console.log(\`listening on http://127.0.0.1:\${port}\`);
});
JS

chown "\${APP_NAME}:\${APP_NAME}" "/srv/\${APP_NAME}/current/server.js"

mkdir -p "/etc/\${APP_NAME}"
cat >"/etc/\${APP_NAME}/\${APP_NAME}.env" <<ENV
NODE_ENV=production
PORT=\${APP_PORT}
ENV
chmod 600 "/etc/\${APP_NAME}/\${APP_NAME}.env"

cat >"/etc/systemd/system/\${APP_NAME}.service" <<UNIT
[Unit]
Description=\${APP_NAME}
After=network.target

[Service]
Type=simple
User=\${APP_NAME}
WorkingDirectory=/srv/\${APP_NAME}/current
EnvironmentFile=/etc/\${APP_NAME}/\${APP_NAME}.env
ExecStart=/usr/bin/node /srv/\${APP_NAME}/current/server.js
Restart=always
RestartSec=2

[Install]
WantedBy=multi-user.target
UNIT

systemctl daemon-reload
systemctl enable --now "\${APP_NAME}.service"

mkdir -p "/var/www/\${DOMAIN}/html"
mkdir -p /var/www/letsencrypt
echo "It works" >"/var/www/\${DOMAIN}/html/index.html"

cat >"/etc/nginx/sites-available/\${DOMAIN}" <<NGINX
server {
  listen 80;
  listen [::]:80;
  server_name \${DOMAIN};

  root /var/www/\${DOMAIN}/html;
  index index.html;

  location ^~ /.well-known/acme-challenge/ {
    root /var/www/letsencrypt;
  }

  location ~ /\\.(?!well-known) {
    return 404;
  }

  location / {
    try_files \\$uri \\$uri/ =404;
  }

  location /api/ {
    proxy_http_version 1.1;
    proxy_set_header Host \\$host;
    proxy_set_header X-Real-IP \\$remote_addr;
    proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto \\$scheme;
    proxy_pass http://127.0.0.1:\${APP_PORT}/;
  }
}
NGINX

ln -sf "/etc/nginx/sites-available/\${DOMAIN}" "/etc/nginx/sites-enabled/\${DOMAIN}"
rm -f /etc/nginx/sites-enabled/default || true

nginx -t
systemctl reload nginx

certbot --nginx -d "\${DOMAIN}" --non-interactive --agree-tos -m "\${EMAIL}" --redirect
certbot renew --dry-run
BASH

같은 분야의 템플릿