Deploy Node.js và PHP Lên AWS EC2: Hướng Dẫn Hoàn Chỉnh Từ A Đến Z
Danh Mục Bài Viết
- 1. AWS EC2 là gì và tại sao dùng để deploy?
- 2. Chuẩn bị trước khi bắt đầu
- 3. Bước 1: Tạo EC2 Instance
- 4. Bước 2: Kết nối SSH và cài môi trường
- 5. Bước 3: Clone code và cài dependencies
- 6. Bước 4: Cài PM2 (Node.js) hoặc cấu hình PHP-FPM, rồi setup Nginx
- 7. Bước 5: Bật HTTPS với Let's Encrypt (miễn phí)
- 8. Lỗi thường gặp và cách fix
- 9. FAQ — Câu hỏi hay gặp nhất
- 10. Sau khi deploy xong — đừng quên những điều này
Mình biết cảm giác đó — code chạy ngon trên local, nhưng khi cần đưa lên server production lại lúng túng không biết bắt đầu từ đâu. AWS EC2 là lựa chọn phổ biến nhất hiện nay, nhưng nó không phải "bấm nút là xong". Có khoảng 7–8 bước cần làm đúng thứ tự, và bỏ sót một bước là app không chạy được — hoặc tệ hơn, chạy được nhưng không bảo mật. Bài này mình viết theo đúng quy trình mình dùng hàng ngày.
AWS EC2 là gì và tại sao dùng để deploy?
EC2 (Elastic Compute Cloud) đơn giản là một máy chủ ảo (VPS) chạy trên hạ tầng của Amazon. Bạn thuê CPU, RAM, ổ đĩa — rồi muốn cài gì, chạy gì, cấu hình thế nào là quyền bạn. Không như Heroku hay Vercel nơi platform kiểm soát môi trường runtime, EC2 cho bạn quyền kiểm soát hoàn toàn.
Lý do nhiều team chọn EC2 để deploy Node.js/PHP: chi phí linh hoạt (t2.micro free tier, t3.small chỉ ~$15/tháng), không giới hạn giờ chạy, không lo cold start, và dễ scale khi cần. Phù hợp cho ứng dụng cần chạy liên tục 24/7 với traffic ổn định.
Chuẩn bị trước khi bắt đầu
Trước khi tạo EC2, cần chuẩn bị:
- Tài khoản AWS: Nếu chưa có, đăng ký tại aws.amazon.com — năm đầu có free tier t2.micro.
- Source code: Đã đẩy lên GitHub/GitLab, hoặc có file zip sẵn.
- Domain (tuỳ chọn): Nếu muốn HTTPS, cần domain trỏ về IP của EC2.
- SSH client: Terminal trên Mac/Linux, hoặc PuTTY trên Windows.
Khổ nỗi là nhiều bạn cứ muốn setup HTTPS ngay từ đầu trước khi app chạy được — làm vậy sẽ rối. Nên đi theo thứ tự: EC2 → app chạy được → Nginx reverse proxy → HTTPS.
Bước 1: Tạo EC2 Instance
Vào AWS Console → EC2 → Launch Instance. Các lựa chọn quan trọng:
- AMI: Chọn Ubuntu 22.04 LTS — phổ biến, nhiều tutorial, package manager apt dễ dùng.
- Instance type: t2.micro (free tier, 1 CPU, 1GB RAM) để test. Production dùng t3.small trở lên.
- Key pair: Tạo mới, tải file
.pemvề máy. Giữ file này, mất là không đăng nhập được. - Security Group: Mở port 22 (SSH), 80 (HTTP), 443 (HTTPS). Nếu app chạy trực tiếp trên port khác (vd 3000), mở thêm port đó để test.
- Storage: Mặc định 8GB, nên tăng lên 20GB để tránh đầy disk sau vài tháng.
Sau khi launch, vào EC2 Dashboard thấy instance đang chạy. Copy Public IPv4 address — đây là địa chỉ bạn sẽ SSH vào.
Bước 2: Kết nối SSH và cài môi trường
Phân quyền file key trước (bắt buộc, nếu không SSH sẽ báo lỗi "Permissions too open"):
chmod 400 /path/to/your-key.pem
ssh -i /path/to/your-key.pem ubuntu@YOUR_EC2_PUBLIC_IPLần đầu kết nối sẽ hỏi xác nhận fingerprint, gõ yes. Vào được rồi thì update hệ thống trước:
sudo apt update && sudo apt upgrade -yCài Node.js (nếu deploy Node.js):
# Cài NVM để quản lý version Node
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install 20
nvm use 20
node -v # kiểm traCài PHP + Composer (nếu deploy PHP/Laravel):
sudo apt install -y php8.2 php8.2-fpm php8.2-mysql php8.2-mbstring php8.2-xml php8.2-curl php8.2-zip
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
php -v # kiểm traNói thật thì bước cài đặt này hay bị lỗi nhất vì Ubuntu mỗi phiên bản có package name khác nhau. Nếu gặp lỗi Package not found, thêm PPA:
sudo add-apt-repository ppa:ondrej/php -y
sudo apt updateBước 3: Clone code và cài dependencies
Clone repo về server:
cd /var/www
sudo mkdir myapp
sudo chown ubuntu:ubuntu myapp
cd myapp
git clone https://github.com/yourusername/yourrepo.git .Nếu repo private, cần thêm SSH key của EC2 vào GitHub. Hoặc dùng Personal Access Token:
git clone https://[email protected]/yourusername/yourrepo.git .Node.js — cài packages và tạo .env:
npm install --production
cp .env.example .env
nano .env # điền biến môi trường thựcLaravel/PHP — cài packages:
composer install --no-dev --optimize-autoloader
cp .env.example .env
php artisan key:generate
nano .env # điền DB_HOST, DB_PASSWORD, APP_URL...
php artisan migrate --force
php artisan config:cache
php artisan route:cacheMẹo: Không bao giờ commit file
.envlên Git. Trên production, tạo file này thủ công rồi điền từ AWS Secrets Manager hoặc copy từ local qua SCP.
Bước 4: Cài PM2 (Node.js) hoặc cấu hình PHP-FPM, rồi setup Nginx
PM2 là process manager cho Node.js — giúp app tự restart khi crash, restart khi server reboot, và xem log dễ dàng:
npm install -g pm2
pm2 start app.js --name "myapp" # hoặc npm run start
pm2 save
pm2 startup # chạy lệnh mà PM2 in ra để enable autostartKiểm tra app đang chạy: pm2 status. Xem log: pm2 logs myapp.
Cài và cấu hình Nginx làm reverse proxy:
sudo apt install -y nginx
# Tạo config file cho site
sudo nano /etc/nginx/sites-available/myappNội dung config cho Node.js (app chạy port 3000):
server {
listen 80;
server_name yourdomain.com; # hoặc dùng IP nếu chưa có domain
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}Nội dung config cho Laravel/PHP:
server {
listen 80;
server_name yourdomain.com;
root /var/www/myapp/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}Kích hoạt config và restart Nginx:
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t # kiểm tra syntax
sudo systemctl restart nginxBước 5: Bật HTTPS với Let's Encrypt (miễn phí)
Chỉ làm bước này sau khi domain đã trỏ về IP của EC2 (DNS propagate mất 5–30 phút):
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.comCertbot tự cấu hình Nginx để dùng HTTPS và auto-renew certificate mỗi 90 ngày. Sau đó test HTTPS tự động renew:
sudo certbot renew --dry-runLỗi thường gặp và cách fix
- 502 Bad Gateway: Nginx không kết nối được đến app. Kiểm tra app có đang chạy không (
pm2 statushoặcsystemctl status php8.2-fpm), và đúng port trong nginx config chưa. - 403 Forbidden (Laravel): Sai quyền thư mục. Chạy:
sudo chown -R www-data:www-data /var/www/myapp/storagevàsudo chmod -R 775 /var/www/myapp/storage. - App không restart sau reboot: Quên chạy
pm2 startupvàpm2 save. Với PHP-FPM thì enable service:sudo systemctl enable php8.2-fpm. - Database connection failed: Kiểm tra Security Group của RDS có cho EC2 security group truy cập chưa, và
DB_HOSTtrong.envphải là endpoint của RDS, không phải localhost. - Disk đầy:
df -hđể kiểm tra.sudo journalctl --vacuum-time=7dđể dọn log cũ. Logs của PM2 có thể rất lớn:pm2 flush.
FAQ — Câu hỏi hay gặp nhất
Q: Nên dùng EC2 hay Elastic Beanstalk cho Node.js/PHP?A: EC2 cho kiểm soát tối đa, phù hợp khi bạn biết mình cần gì. Elastic Beanstalk tự động hóa deploy nhưng tốn tiền hơn và khó debug hơn. Mình khuyên EC2 cho team nhỏ dưới 5 người — ít abstraction, dễ troubleshoot hơn.
Q: Nếu app có nhiều microservices thì sao?A: Mỗi service một EC2 sẽ đắt. Thay vào đó dùng Docker + ECS Fargate (serverless container) hoặc một EC2 to hơn chạy nhiều container bằng Docker Compose. Bài sau mình sẽ viết về deploy với Docker trên EC2.
Q: Làm sao deploy tự động khi push code lên GitHub?A: Dùng GitHub Actions. Tạo workflow chạy
sshvào EC2,git pull,npm install,pm2 restart. Cần lưu SSH private key vào GitHub Secrets. Hoặc dùng AWS CodeDeploy — phức tạp hơn nhưng chuyên nghiệp hơn.
Q: Có cần Elastic IP không?A: Có, nếu bạn dùng domain. Khi restart EC2, IP public thay đổi — Elastic IP giữ IP cố định. Miễn phí khi đang dùng, mất tiền khi không gắn vào instance nào (~$4/tháng).
Sau khi deploy xong — đừng quên những điều này
Deploy lên EC2 không phải điểm kết thúc mà là điểm bắt đầu của việc vận hành. Nghĩ mà xem — một app production cần monitor uptime, rotate log định kỳ, backup database, và có kế hoạch khi server quá tải. Tối thiểu hãy setup CloudWatch để nhận alert khi CPU vượt 80% hoặc disk đầy.
Bước tiếp theo sau khi app đã chạy: cân nhắc đặt RDS thay vì MySQL local trên EC2 (để tách DB và app riêng, dễ scale), và dùng S3 để lưu static files thay vì ổ đĩa EC2. Hai thứ này sẽ thay đổi đáng kể độ ổn định của hệ thống về dài hạn.
