AWS Deployment for Node.js | EC2
이 글의 핵심
Deploy Node.js on AWS: EC2 bootstrap with PM2 and Nginx, Elastic Beanstalk with eb CLI, and serverless Lambda with serverless-http. Security groups, env vars, HTTPS, and rollout strategies.
Three AWS Deployment Paths
AWS offers a spectrum of control vs convenience for Node.js applications:
| Option | Control | Maintenance | Best for |
|---|---|---|---|
| EC2 | Full | You manage OS, runtime, web server | Learning, custom configs, cost optimization |
| Elastic Beanstalk | Medium | AWS manages infra, you manage app | Standard web apps without Kubernetes complexity |
| Lambda | Low | AWS manages everything | Spiky/low traffic, microservices, event-driven |
Option 1: EC2 with PM2 and Nginx
EC2 gives you a virtual machine. You install Node.js, PM2 for process management, and Nginx as a reverse proxy.
Launch an EC2 Instance
- AWS Console → EC2 → Launch Instance
- Choose Ubuntu Server 22.04 LTS (or 24.04 LTS)
- Instance type:
t3.micro(free tier) ort3.smallfor production - Security Group: create rules:
- SSH (22) — from your IP only, not 0.0.0.0/0
- HTTP (80) — from anywhere (0.0.0.0/0)
- HTTPS (443) — from anywhere
- Key pair: create and download a
.pemfile - Launch
Initial Server Setup
# Connect via SSH
ssh -i ~/keys/myapp.pem ubuntu@<your-ec2-public-ip>
# Update packages
sudo apt-get update && sudo apt-get upgrade -y
# Install Node.js 20 LTS via NodeSource
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
# Verify
node --version # v20.x.x
npm --version
# Install git
sudo apt-get install -y git
Deploy Your Application
# Clone the repository
git clone https://github.com/yourusername/your-app.git
cd your-app
# Install production dependencies only
npm ci --only=production
# Create .env file
cat > .env << 'EOF'
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://user:password@db-host:5432/mydb
JWT_SECRET=your-secret-here
EOF
# Restrict .env permissions
chmod 600 .env
PM2 Process Management
# Install PM2 globally
sudo npm install -g pm2
# Start the application (cluster mode uses all CPU cores)
pm2 start app.js --name "my-app" -i max
# Or use an ecosystem config file for better control
cat > ecosystem.config.js << 'EOF'
module.exports = {
apps: [{
name: 'my-app',
script: 'app.js',
instances: 'max', // one per CPU core
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
max_memory_restart: '500M', // restart if memory exceeds 500MB
error_file: './logs/err.log',
out_file: './logs/out.log',
log_file: './logs/combined.log',
}]
};
EOF
pm2 start ecosystem.config.js
# Set up startup script (runs PM2 on server reboot)
pm2 startup
# Run the command it prints (something like: sudo env PATH=...)
pm2 save
# Useful PM2 commands
pm2 list # show all processes
pm2 logs # tail all logs
pm2 restart my-app # restart without downtime
pm2 reload my-app # zero-downtime reload (cluster mode)
pm2 monit # real-time monitoring dashboard
Nginx Configuration
sudo apt-get install -y nginx
# Create site config
sudo tee /etc/nginx/sites-available/my-app << 'EOF'
server {
listen 80;
server_name your-domain.com www.your-domain.com;
# Serve static files directly — bypass Node.js
location /static/ {
alias /home/ubuntu/your-app/public/;
expires 1y;
add_header Cache-Control "public, immutable";
}
# Proxy everything else to Node.js
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade'; # WebSocket support
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_cache_bypass $http_upgrade;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
EOF
# Enable the site
sudo ln -s /etc/nginx/sites-available/my-app /etc/nginx/sites-enabled/
sudo nginx -t # validate config
sudo systemctl reload nginx
HTTPS with Let’s Encrypt
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com -d www.your-domain.com
# Certbot automatically modifies the nginx config for HTTPS
# Auto-renewal is set up automatically
sudo certbot renew --dry-run # test renewal
Deployment Updates
# Update script — run on server
cd /home/ubuntu/your-app
git pull origin main
npm ci --only=production
# Build if needed (TypeScript, etc.)
npm run build
# Zero-downtime reload (PM2 cluster mode)
pm2 reload my-app
Option 2: Elastic Beanstalk
Elastic Beanstalk manages the EC2 instances, load balancer, auto-scaling, and deployment for you. You focus on the application.
Setup
# Install EB CLI
pip install awsebcli --upgrade
# Configure AWS credentials
aws configure
# Enter: Access Key ID, Secret Access Key, region (e.g. us-east-1), output format (json)
# In your project directory
cd your-app
eb init
# Select:
# - Region (us-east-1 or ap-northeast-2 for Korea)
# - Application name
# - Platform: Node.js 20 (running on 64bit Amazon Linux 2023)
# - Whether to use CodeCommit: No (use GitHub)
Create Environment and Deploy
# Create a production environment (this launches EC2 instances + load balancer)
eb create production --instance-type t3.small --min-instances 1 --max-instances 3
# Deploy current code
eb deploy
# Check status
eb status
# Tail logs
eb logs --all
# SSH into the instance for debugging
eb ssh
Environment Variables
# Set environment variables (replaces .env file)
eb setenv \
NODE_ENV=production \
PORT=8080 \
DATABASE_URL=postgresql://user:password@db-host:5432/mydb \
JWT_SECRET=your-secret-here
# View current env vars
eb printenv
Elastic Beanstalk Procfile
# Procfile — tells EB how to start your app
web: node app.js
Or with npm:
web: npm start
Health Check Endpoint
Elastic Beanstalk pings / by default for health checks. Add a dedicated endpoint:
// In your Express app
app.get('/health', (req, res) => {
res.status(200).json({ status: 'ok', timestamp: new Date().toISOString() });
});
Then configure EB to use /health:
eb config
# Set HealthCheckPath: /health under aws:elasticbeanstalk:application
Option 3: AWS Lambda (Serverless)
Lambda runs your code in response to events. For HTTP APIs, use API Gateway → Lambda.
Wrap Your Express App
// lambda.js
const serverless = require('serverless-http');
const app = require('./app'); // your Express app
module.exports.handler = serverless(app);
npm install --save serverless-http
serverless.yml Configuration
service: my-app
provider:
name: aws
runtime: nodejs20.x
region: ap-northeast-2
timeout: 30
memorySize: 512
environment:
NODE_ENV: production
DATABASE_URL: ${ssm:/my-app/database-url} # from AWS SSM Parameter Store
functions:
app:
handler: lambda.handler
events:
- http:
path: /
method: ANY
cors: true
- http:
path: /{proxy+}
method: ANY
cors: true
Deploy
npm install -g serverless
serverless deploy --stage production
# View logs
serverless logs -f app --tail
# Invoke manually
serverless invoke -f app --data '{"httpMethod":"GET","path":"/"}'
# Tear down
serverless remove --stage production
Cold Start Mitigation
Lambda has cold starts (first invocation after idle takes extra time). Reduce impact:
# Provisioned concurrency — pre-warm instances (adds cost)
functions:
app:
handler: lambda.handler
provisionedConcurrency: 2
Or use a CloudWatch Events rule to ping Lambda every 5 minutes:
events:
- schedule:
rate: rate(5 minutes)
enabled: true
input:
source: warm-up
Choosing Between the Three
Low traffic / spiky / event-driven
→ Lambda
Standard web app, team unfamiliar with containers
→ Elastic Beanstalk
Custom config, cost-sensitive, or needs containers later
→ EC2 + PM2 + Nginx
→ EC2 + Docker (next step)
→ ECS / EKS (after Docker)
Key Takeaways
- EC2 + PM2 + Nginx: full control, requires most ops work. PM2 handles cluster mode and auto-restart; Nginx handles HTTPS, static files, and proxying; Let’s Encrypt is free SSL
- Elastic Beanstalk: AWS manages infrastructure. Deploy with
eb deploy, set env vars witheb setenv. Add a/healthendpoint for health checks - Lambda: pay per request, no idle cost, auto-scales. Use
serverless-httpto wrap Express. Cold starts are real — use provisioned concurrency for latency-sensitive APIs - Security: security groups should only open the ports you use. Never put secrets in code — use environment variables, AWS SSM Parameter Store, or Secrets Manager
- Zero-downtime deploys: PM2 cluster mode supports
pm2 reload(graceful); Elastic Beanstalk supports rolling deployments out of the box
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Deploy Node.js on AWS: EC2 bootstrap with PM2 and Nginx, Elastic Beanstalk with eb CLI, and serverless Lambda with serve… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- Express.js 완벽 가이드 | Node.js 웹 프레임워크
- Docker Compose로 Node API·PostgreSQL·Redis 한 번에 띄우기
- Node.js 테스트 | Jest, Mocha, Supertest 완벽 가이드
이 글에서 다루는 키워드 (관련 검색어)
AWS, EC2, Node.js, Deployment, Elastic Beanstalk, Lambda, Nginx, PM2 등으로 검색하시면 이 글이 도움이 됩니다.