AWS Elastic Beanstalk Node.js CI/CD SSL

Deploying Node.js to AWS Elastic Beanstalk with SSL, Auto Scaling, and GitHub Actions CI/CD

AWS Elastic Beanstalk is one of the fastest ways to get a Node.js application running in a production-grade environment with load balancing, auto scaling, health monitoring, and managed OS updates — without managing EC2 instances directly. In this guide I'll walk through the full setup: deploying the app, attaching a custom domain with SSL, and wiring up automated deployments via GitHub Actions.

Prerequisites: AWS account, Node.js app, domain registered in Route 53 or transferable to it, GitHub repository, AWS CLI installed.

1. Prepare Your Node.js App

Elastic Beanstalk expects your app to listen on the port exposed by the PORT environment variable (it injects this automatically).

// server.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

app.get('/health', (req, res) => res.json({ status: 'ok' }));

app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

Ensure your package.json has a start script:

{
  "scripts": {
    "start": "node server.js"
  },
  "engines": {
    "node": ">=20"
  }
}

Important: Add node_modules/ to .gitignore. Beanstalk runs npm install on deploy — don't include node_modules in your zip.

2. Create the Elastic Beanstalk Application

Via AWS Console

  1. Go to Elastic Beanstalk → Create Application
  2. Platform: Node.js, choose the latest branch
  3. Application code: upload a zip of your project (without node_modules)
  4. Presets: choose High availability (this enables load balancer + auto scaling)

Via EB CLI (recommended)

npm install -g aws-elastic-beanstalk-cli  # if not installed
eb init my-node-app --platform node.js --region ca-central-1
eb create production --elb-type application
eb open

3. Configure Environment Variables

Never hard-code secrets. Set them as Beanstalk environment properties:

eb setenv NODE_ENV=production \
  DATABASE_URL=postgresql://... \
  JWT_SECRET=your-secret \
  OPENAI_API_KEY=sk-...

Or via Console: Configuration → Software → Environment properties. These are injected as process.env.* at runtime.

4. Request an SSL Certificate with ACM

  1. Open AWS Certificate Manager → Request certificate
  2. Enter your domain: api.yourdomain.com (and optionally *.yourdomain.com)
  3. Choose DNS validation → click Request
  4. ACM shows a CNAME record — add it to Route 53 (there's a one-click "Create records in Route 53" button)
  5. Wait ~2 minutes for validation status to turn Issued

5. Attach SSL to the Load Balancer

  1. In Beanstalk: Configuration → Load Balancer → Edit
  2. Add a listener: Port 443, Protocol HTTPS, SSL certificate → select your ACM cert
  3. Set the HTTP (port 80) listener to redirect to HTTPS
  4. Apply changes — Beanstalk rolls out with no downtime

6. Point Your Custom Domain in Route 53

# Route 53 → Hosted Zone → Create Record
Name:    api.yourdomain.com
Type:    A (Alias)
Target:  Alias to Elastic Beanstalk environment → select your env

Using an Alias A record (not CNAME) avoids extra DNS lookups and works at the zone apex (yourdomain.com).

7. Configure Auto Scaling

In Beanstalk: Configuration → Capacity → Edit

Environment type: Load balanced
Min instances:    2
Max instances:    6

Scaling trigger:
  Metric:       CPUUtilization
  Upper:        70%  → scale out
  Lower:        30%  → scale in
  Period:       5 minutes

Two minimum instances ensures high availability across two Availability Zones. The load balancer health check endpoint (/health) should return HTTP 200 within 10 seconds or Beanstalk will replace the instance.

8. Automated Deployments with GitHub Actions

Create .github/workflows/deploy.yml:

name: Deploy to Elastic Beanstalk

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install & test
        run: |
          npm ci
          npm test

      - name: Generate deployment package
        run: zip -r deploy.zip . -x "*.git*" -x "node_modules/*"

      - name: Deploy to Elastic Beanstalk
        uses: einaregilsson/beanstalk-deploy@v22
        with:
          aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          application_name: my-node-app
          environment_name: production
          region: ca-central-1
          version_label: ${{ github.sha }}
          deployment_package: deploy.zip

Add AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as GitHub repository secrets. Use an IAM user with only AWSElasticBeanstalkFullAccess — never use root credentials in CI.

Common Issues & Fixes

Conclusion

You now have a Node.js app running on Elastic Beanstalk with HTTPS, a custom domain, auto scaling across multiple AZs, and zero-downtime deployments triggered on every push to main. This is the exact stack I've used across multiple production projects handling thousands of daily requests.

Need help setting up your AWS infrastructure? Get in touch or hire me on Upwork.