AWS EC2 Deployment Guide for Web Applications

Complete AWS EC2 deployment guide: instance selection, security groups, Node.js deployment, Nginx reverse proxy, SSL, auto-scaling, CloudWatch monitoring, and cost optimization.

E
ECOSIRE Research and Development Team
|19. März 202610 Min. Lesezeit2.1k Wörter|

AWS EC2-Bereitstellungshandbuch für Webanwendungen

EC2 bleibt die flexibelste Rechenoption in AWS für Webanwendungen, die konsistente Leistung, benutzerdefinierte Software-Stacks und vorhersehbare Preise erfordern. Während ECS, EKS und Lambda in der Cloud-nativen Welt mehr Aufmerksamkeit erhalten, erhalten Sie mit EC2 einen Server, den Sie vollständig kontrollieren – keine Komplexität der Container-Orchestrierung, keine Kaltstartlatenz, keine überraschenden Aufrufkosten.

Dieser Leitfaden behandelt die Bereitstellung einer Produktions-Node.js-Webanwendung auf EC2: Instanzauswahl, Sicherheitsgruppenkonfiguration, Anwendungsbereitstellung, Nginx-Reverse-Proxy, SSL mit Cloudflare, Überwachung mit CloudWatch und die laufenden Wartungsmuster, die eine EC2-Bereitstellung fehlerfrei halten.

Wichtige Erkenntnisse

  • t3.large ist der richtige Ausgangspunkt für eine Full-Stack-Node.js + PostgreSQL-Bereitstellung
  • Verwenden Sie Ubuntu 24.04 LTS – unterstützt bis 2029, umfassend dokumentiert, ausgezeichnete Paketverfügbarkeit
  • Elastic IP ist obligatorisch – Ihre EC2-IP ändert sich bei jedem Stopp/Start ohne sie – Sicherheitsgruppen sind zustandsbehaftet – Sie benötigen nur eingehende Regeln; Ausgehend ist normalerweise „Alles zulassen“. – Speichern Sie Ihren Bereitstellungs-SSH-Schlüssel in einer separaten .pem-Datei; Begebe es niemals Git – Verwenden Sie nach Möglichkeit EC2-Instanzverbindung oder Session Manager anstelle von direktem SSH (keine Schlüsselverwaltung). – Der CloudWatch-Agent liefert Ihnen Speicher- und Festplattenmetriken (standardmäßig nicht verfügbar). – Reserved Instances oder Savings Plans reduzieren die EC2-Kosten um 40–60 % im Vergleich zu On-Demand

Instanzauswahl

Der richtige Instanztyp hängt von Ihrer Arbeitslast ab:

ArbeitsbelastungEmpfohlene InstanzvCPURAMKosten/Monat
Light (Blog, kleine App)t3.small22GB~18 $
Mittel (Full-Stack-App)t3.medium24GB~35 $
Produktion (Multiservice)t3.large28GB~70 $
Schwer (API mit hohem Datenverkehr)c6i.xlarge48GB~140 $
Speicherintensiv (ML/Cache)r6i.large216 GB~120 $

Für ein Monorepo mit 5 Node.js-Anwendungen (Next.js, NestJS, Docusaurus, 2 Markenseiten) plus Docker-Infrastruktur (PostgreSQL, Redis, Authentik) ist ein t3.large die minimal praktikable Konfiguration. Die t3-Familie nutzt „Burst“-Leistung – die Leistung ist im Normalbetrieb ausgezeichnet, eine anhaltend hohe CPU-Leistung führt jedoch zu einer Drosselung.

Verwenden Sie für konstant hohe CPU-Auslastungen (Videoverarbeitung, ML-Inferenz, starke Kryptografie) stattdessen c6i-Instanzen (rechenoptimiert).


Ersteinrichtung des Servers

Nach dem Start Ihrer EC2-Instanz mit Ubuntu 24.04:

# Connect via SSH
ssh -i your-key.pem [email protected]

# Update system packages
sudo apt update && sudo apt upgrade -y

# Install essential tools
sudo apt install -y \
  git curl wget unzip \
  build-essential \
  nginx \
  certbot python3-certbot-nginx \
  docker.io docker-compose-plugin \
  htop ncdu iotop

# Install Node.js via NVM (allows easy version management)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install 22
nvm use 22
nvm alias default 22

# Install pnpm
curl -fsSL https://get.pnpm.io/install.sh | sh -
source ~/.bashrc

# Install PM2 globally
npm install -g pm2

# Install PM2 log rotation immediately
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 50M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:compress true

Sicherheitsgruppenkonfiguration

Die Sicherheitsgruppe ist die Firewall Ihrer EC2-Instanz. Konfigurieren Sie es sorgfältig:

Inbound Rules:
┌─────────┬──────────┬─────────────┬──────────────────────────────────┐
│ Type    │ Protocol │ Port Range  │ Source                           │
├─────────┼──────────┼─────────────┼──────────────────────────────────┤
│ SSH     │ TCP      │ 22          │ Your IP only (not 0.0.0.0/0!)    │
│ HTTP    │ TCP      │ 80          │ 0.0.0.0/0 (Cloudflare IPs only)  │
│ HTTPS   │ TCP      │ 443         │ 0.0.0.0/0 (Cloudflare IPs only)  │
└─────────┴──────────┴─────────────┴──────────────────────────────────┘

Note: Internal app ports (3000, 3001, 3002, etc.) should NOT be
      in the security group — traffic goes through Nginx only

Beschränken Sie für Cloudflare-Proxy-Domänen HTTP/HTTPS auf Cloudflare-IP-Bereiche:

# Cloudflare IPv4 ranges — restrict port 80/443 source to these
103.21.244.0/22
103.22.200.0/22
103.31.4.0/22
104.16.0.0/13
104.24.0.0/14
108.162.192.0/18
131.0.72.0/22
141.101.64.0/18
162.158.0.0/15
172.64.0.0/13
173.245.48.0/20
188.114.96.0/20
190.93.240.0/20
197.234.240.0/22
198.41.128.0/17

Dies verhindert den direkten Zugriff auf Ihren Server und umgeht den WAF- und DDoS-Schutz von Cloudflare.


Anwendungsbereitstellung

# Create application directory
sudo mkdir -p /opt/ecosire/app
sudo chown ubuntu:ubuntu /opt/ecosire/app

# Clone the repository
git clone https://github.com/your-org/your-repo.git /opt/ecosire/app
cd /opt/ecosire/app

# Create .env.local from template
cp .env.example .env.local
# Edit with production values
nano .env.local

# Install dependencies
pnpm install --frozen-lockfile

# Build everything
npx turbo run build

# Run database migrations
pnpm --filter @ecosire/db db:migrate

# Start infrastructure (PostgreSQL, Redis, Authentik)
docker compose -f infrastructure/docker-compose.dev.yml up -d

# Wait for services to be healthy
sleep 30

# Start Node.js applications
pm2 start ecosystem.config.cjs

# Save process list for reboot persistence
pm2 save

# Configure PM2 to start on system boot
pm2 startup
# Run the command it outputs

Elastic IP und DNS

Die öffentliche IP einer EC2-Instanz ändert sich jedes Mal, wenn Sie sie stoppen und starten. Elastic IP stellt eine permanente IP bereit:

# In AWS Console:
# 1. EC2 > Network & Security > Elastic IPs
# 2. Allocate Elastic IP address
# 3. Associate it with your instance

# Your IP is now permanent — update Cloudflare DNS to point to it
# A record: ecosire.com → 13.223.116.181 (your Elastic IP)
# A record: api.ecosire.com → 13.223.116.181
# A record: auth.ecosire.com → 13.223.116.181

Stellen Sie diese Datensätze in Cloudflare für den Webverkehr auf „Proxied“ (orangefarbene Wolke) ein. Der Cloudflare-Proxy verbirgt Ihre tatsächliche EC2-IP und bietet so DDoS-Schutz.


Speicher: EBS-Volume-Management

EC2-Instanzen umfassen ein Root-EBS-Volume. Für die Produktion benötigen Sie ausreichend Platz für Build-Artefakte, Protokolle und Docker-Daten:

# Check current disk usage
df -h

# Check which directories are consuming space
ncdu /

# Typical space requirements for a 5-app monorepo:
# - /opt/ecosire/app: ~2GB (code + node_modules + .next builds)
# - Docker data (/var/lib/docker): ~5GB
# - PM2 logs (/var/log/pm2): ~1GB (with rotation)
# - System: ~5GB
# Total: ~13GB minimum, recommend 30GB+ root volume

# If you need to resize an EBS volume (no downtime needed):
# 1. In AWS Console: EC2 > Volumes > Modify Volume
# 2. After resize completes, grow the filesystem:
sudo growpart /dev/xvda 1
sudo resize2fs /dev/xvda1

CloudWatch-Überwachung

EC2 stellt standardmäßig grundlegende CPU- und Netzwerkmetriken bereit. Installieren Sie für Speicher- und Festplattenmetriken den CloudWatch-Agenten:

# Download and install CloudWatch agent
wget https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb
sudo dpkg -i amazon-cloudwatch-agent.deb

# Create configuration
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-config-wizard
// /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json
{
  "agent": {
    "metrics_collection_interval": 60,
    "run_as_user": "cwagent"
  },
  "metrics": {
    "append_dimensions": {
      "AutoScalingGroupName": "${aws:AutoScalingGroupName}",
      "ImageId": "${aws:ImageId}",
      "InstanceId": "${aws:InstanceId}",
      "InstanceType": "${aws:InstanceType}"
    },
    "metrics_collected": {
      "mem": {
        "measurement": ["mem_used_percent"],
        "metrics_collection_interval": 60
      },
      "disk": {
        "measurement": ["used_percent"],
        "metrics_collection_interval": 60,
        "resources": ["/", "/opt/ecosire"]
      }
    }
  },
  "logs": {
    "logs_collected": {
      "files": {
        "collect_list": [
          {
            "file_path": "/var/log/pm2/ecosire-api.err.log",
            "log_group_name": "/ec2/ecosire/api-errors",
            "log_stream_name": "{instance_id}"
          },
          {
            "file_path": "/var/log/nginx/ecosire-error.log",
            "log_group_name": "/ec2/ecosire/nginx-errors",
            "log_stream_name": "{instance_id}"
          }
        ]
      }
    }
  }
}
# Start the agent
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
  -a fetch-config \
  -m ec2 \
  -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json \
  -s

Automatisierte Backups

Richten Sie automatisierte PostgreSQL-Backups auf S3 ein:

# Create backup script
cat > /opt/ecosire/scripts/backup-db.sh << 'EOF'
#!/bin/bash
set -e

DATE=$(date +%Y-%m-%d-%H%M%S)
BACKUP_FILE="/tmp/ecosire-db-${DATE}.sql.gz"
S3_BUCKET="s3://your-backups-bucket/postgres"

# Dump the database (connects via Docker network)
docker exec ecosire-postgres pg_dump \
  -U ecosire \
  -d ecosire_dev \
  --no-owner \
  --no-privileges \
  | gzip > "$BACKUP_FILE"

# Upload to S3
aws s3 cp "$BACKUP_FILE" "$S3_BUCKET/"

# Clean up local file
rm "$BACKUP_FILE"

# Delete backups older than 30 days from S3
aws s3 ls "$S3_BUCKET/" \
  | awk '{print $4}' \
  | sort \
  | head -n -30 \
  | xargs -I {} aws s3 rm "$S3_BUCKET/{}" 2>/dev/null || true

echo "Backup complete: ${DATE}"
EOF

chmod +x /opt/ecosire/scripts/backup-db.sh

# Schedule daily backups at 3 AM UTC
(crontab -l 2>/dev/null; echo "0 3 * * * /opt/ecosire/scripts/backup-db.sh >> /var/log/db-backup.log 2>&1") | crontab -

IAM-Rollenkonfiguration

Fügen Sie Ihrer EC2-Instanz eine IAM-Rolle für den AWS-Servicezugriff (S3, CloudWatch, SES) hinzu:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::your-products-bucket",
        "arn:aws:s3:::your-products-bucket/*",
        "arn:aws:s3:::your-backups-bucket",
        "arn:aws:s3:::your-backups-bucket/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "ses:SendEmail",
        "ses:SendRawEmail"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "cloudwatch:PutMetricData",
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    }
  ]
}

Wenn die IAM-Rolle Ihrer Instanz zugeordnet ist, verwenden AWS SDK-Aufrufe automatisch Instanzanmeldeinformationen – es ist kein Zugriffsschlüssel/geheimer Schlüssel in Ihren Umgebungsvariablen erforderlich.


Häufige Fallstricke und Lösungen

Falle 1: Keine elastische IP – IP-Änderungen beim Neustart

Durch das Stoppen und Starten (nicht Neustarten) einer EC2-Instanz wird eine neue öffentliche IP zugewiesen. Ohne eine elastische IP geht Ihr DNS kaputt. Weisen Sie unmittelbar nach dem Start Ihrer Instanz eine elastische IP zu und verknüpfen Sie sie.

Falle 2: SSH-Zugriff gesperrt

Wenn Sie Ihren SSH-Schlüssel verlieren oder sich durch eine Fehlkonfiguration von Sicherheitsgruppen aussperren, verwenden Sie EC2 Instance Connect (browserbasiertes SSH) über die AWS-Konsole oder den Session Manager (erfordert die Installation des SSM-Agenten, der standardmäßig mit Ubuntu geliefert wird). Als letzten Ausweg trennen Sie das Root-EBS-Volume, hängen es an eine andere Instanz an, reparieren die Datei „authorized_keys“ und hängen es erneut an.

Falle 3: Während der Bereitstellung ist nicht genügend Speicherplatz vorhanden

Der Build-Cache .next und node_modules wachsen während der Entwicklung erheblich. Überwachen Sie die Festplattennutzung mit df -h und stellen Sie einen CloudWatch-Alarm auf disk_used_percent > 80 % ein. Der Befehl ncdu (ncdu /) identifiziert, welche Verzeichnisse Speicherplatz verbrauchen.

Falle 4: Speichererschöpfung durch Node.js OOM

Node.js hat ein Standardspeicherlimit (1,4 GB bei 64-Bit), das bei großen Anwendungen zu OOM-Abstürzen führen kann. Legen Sie node_args: '--max-old-space-size=1024' in Ihrer PM2-Ökosystemdatei fest, um die Speichernutzung explizit zu begrenzen. Stellen Sie max_memory_restart etwas höher ein, um bei Überschreitung einen automatischen Neustart durchzuführen.

Falle 5: T3-CPU-Drosselung unter anhaltender Last

T3-Instanzen nutzen „CPU-Credits“ für Spitzenleistung. Längere Vorgänge mit hoher CPU-Leistung (große Builds, umfangreiche Datenbankabfragen) erschöpfen das Guthaben, was zu einer Drosselung der „Grundleistung“ führt. Überwachen Sie CPUCreditBalance in CloudWatch. Wenn das Guthaben ständig aufgebraucht ist, führen Sie ein Upgrade auf eine c6i-Instanz durch oder aktivieren Sie den „unbegrenzten“ Modus (zusätzliche Kosten pro CPU-Stunde über dem Basiswert).


Häufig gestellte Fragen

Soll ich EC2 oder einen verwalteten Dienst wie AWS Elastic Beanstalk verwenden?

EC2 gibt Ihnen die volle Kontrolle: die genaue Node.js-Version, Dateisystemzugriff, die Möglichkeit, Docker-Sidecar-Container auszuführen und benutzerdefinierte Nginx-Konfiguration. Elastic Beanstalk verwaltet die zugrunde liegende Infrastruktur, schränkt jedoch Ihre Optionen ein und erhöht die Komplexität bei der Fehlerbehebung. Für ein Team, das mit der Linux-Serververwaltung vertraut ist, ist EC2 mit PM2 + Nginx einfacher und vorhersehbarer als verwaltete Plattformen. Verwenden Sie Beanstalk, wenn Sie möchten, dass die Plattform die Skalierung und das Gesundheitsmanagement automatisch übernimmt.

Wie gehe ich mit Bereitstellungen ohne Ausfallzeiten auf EC2 um?

Der PM2-Befehl pm2 reload sorgt für null Ausfallzeiten für Cluster-Modus-Prozesse (NestJS-API). Erstellen Sie für Next.js (Fork-Modus) zuerst die neue Version und laden Sie dann PM2 neu. Während der wenigen Sekunden, die PM2 benötigt, um Prozesse zu wechseln, stellt Nginx eingehende Anfragen in die Warteschlange (mit einer kleinen Zeitüberschreitung). Für wirklich null Ausfallzeiten verwenden Sie zwei EC2-Instanzen hinter einem ALB (Application Load Balancer) und stellen diese auf einer bereit, während die andere den Datenverkehr verarbeitet.

Wann sollte ich die automatische Skalierung verwenden?

Die automatische Skalierung erhöht die betriebliche Komplexität erheblich – Zustandsprüfungen, Startvorlagen, Lastausgleichsfunktionen und Überlegungen zur Sitzungsaffinität. Für Anwendungen mit vorhersehbarem Datenverkehr ist eine richtig dimensionierte EC2-Instanz mit vertikaler Skalierung (größerer Instanztyp) einfacher und oft kostengünstiger als horizontale automatische Skalierung. Erwägen Sie die automatische Skalierung, wenn Sie Traffic-Spitzen haben, die mehr als das Fünffache des Basiswerts betragen und die Kosten für den Betrieb einer dauerhaft größeren Instanz die Komplexität der automatischen Skalierung übersteigen.

Wie migriere ich später von EC2 zu Containern?

Beginnen Sie mit der Containerisierung Ihrer Anwendung mit Docker (schreiben Sie eine Docker-Datei für jede App). Testen Sie es lokal mit Docker Compose. Dann wählen Sie zwischen ECS Fargate (serverlose Container, einfacher) oder EKS (Kubernetes, leistungsfähiger, aber komplexer). Die Migration ist unterbrechungsfrei, wenn Sie die Containerisierung inkrementell durchführen – führen Sie die Containerversion hinter demselben Nginx/Cloudflare-Setup aus, überprüfen Sie das Verhalten und stellen Sie dann um.

Was ist die kostengünstigste Möglichkeit, EC2 in der Produktion auszuführen?

Erwerben Sie eine einjährige Reserved Instance (keine oder teilweise Vorauszahlung) für Ihre Basisinstanz – 40 % günstiger als bei Bedarf. Für zusätzliche Kapazität bei Datenverkehrsspitzen verwenden Sie Spot-Instanzen (bis zu 90 % günstiger), wenn Ihre Anwendung Unterbrechungen bewältigen kann. Richten Sie einen CloudWatch-Abrechnungsalarm bei 80 % Ihres Monatsbudgets ein, damit unerwartete Kostensteigerungen frühzeitig erkannt werden. Für produktive Webanwendungen bieten Reserved Instances das beste Gleichgewicht zwischen Kosten und Zuverlässigkeit.


Nächste Schritte

Das Ausführen einer Produktions-Webanwendung auf EC2 erfordert kontinuierliche betriebliche Aufmerksamkeit – Sicherheitspatches, Festplattenverwaltung, Leistungsüberwachung und Bereitstellungsautomatisierung. ECOSIRE führt eine EC2 t3.large-Produktionsinstanz aus, die 5 Anwendungen über mehrere Domänen hinweg bedient, mit automatisierten Backups, CloudWatch-Überwachung und PM2-Bereitstellungen ohne Ausfallzeiten.

Ganz gleich, ob Sie AWS-Infrastrukturberatung, EC2-Bereitstellungseinrichtung oder vollständigen DevOps-Support für Ihre Node.js-Anwendung benötigen, [entdecken Sie unsere Dienste] (/services), um zu erfahren, wie wir Ihnen helfen können.

E

Geschrieben von

ECOSIRE Research and Development Team

Entwicklung von Enterprise-Digitalprodukten bei ECOSIRE. Einblicke in Odoo-Integrationen, E-Commerce-Automatisierung und KI-gestützte Geschäftslösungen.

Chatten Sie auf WhatsApp