Ngôn ngữ hiển thị:
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.
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.
Trước khi tạo EC2, cần chuẩn bị:
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.
Vào AWS Console → EC2 → Launch Instance. Các lựa chọn quan trọng:
.pem về máy. Giữ file này, mất là không đăng nhập được.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.
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 -y# 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 trasudo 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 update
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 .npm install --production
cp .env.example .env
nano .env # điền biến môi trường thựccomposer 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.
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.
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 nginxChỉ 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-runpm2 status hoặc systemctl status php8.2-fpm), và đúng port trong nginx config chưa.sudo chown -R www-data:www-data /var/www/myapp/storage và sudo chmod -R 775 /var/www/myapp/storage.pm2 startup và pm2 save. Với PHP-FPM thì enable service: sudo systemctl enable php8.2-fpm.DB_HOST trong .env phải là endpoint của RDS, không phải localhost.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.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).
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.