Xiro The Dev | Blog
PortfolioBlogTagsAbout
Published on
Sunday, November 2, 2025

Hướng dẫn Deploy PHP lên VPS: Self-hosting với Docker và Nginx

Authors
  • avatar
    Name
    Xiro The Dev
    Twitter

Deploy PHP lên VPS là một kỹ năng cần thiết cho các developer muốn tự quản lý infrastructure của mình. So với các shared hosting services, việc self-host trên VPS cho bạn kiểm soát hoàn toàn, tùy chỉnh cao và hiệu năng tốt hơn. Bài viết này sẽ hướng dẫn bạn deploy PHP app lên VPS sử dụng Docker, MySQL và Nginx.

Table of Contents
  • Tổng quan
  • Prerequisites
  • Kiến trúc Deployment
  • Setup Server
  • Cài đặt Docker và Docker Compose
  • Setup Nginx
  • Cấu hình PHP Application
  • Cấu hình MySQL
  • Deploy Script
  • SSL Certificate với Let's Encrypt
  • Rate Limiting và Security
  • Update và Maintenance
  • Troubleshooting
  • Best Practices
  • Kết luận

Tổng quan

Hướng dẫn này sẽ hướng dẫn bạn deploy ứng dụng PHP đơn giản với MySQL database trên Ubuntu server sử dụng Docker và Nginx. Chúng ta sẽ sử dụng ví dụ từ thư mục examples trong repository này.

📦 Xem source code trên GitHub

Stack công nghệ:

ComponentTechnologyMục đích
ApplicationPHP 8.2Server-side scripting language
DatabaseMySQL 8.0Relational database
ContainerizationDocker & Docker ComposeIsolate và quản lý containers
Web ServerNginxReverse proxy, SSL termination, PHP-FPM handler
PHP RuntimePHP-FPMFastCGI Process Manager cho PHP
SSLLet's EncryptHTTPS encryption
OSUbuntu LinuxServer operating system

Tại sao Self-hosting PHP?

Ưu điểm:

  • ✅ Kiểm soát hoàn toàn infrastructure
  • ✅ Chi phí có thể thấp hơn managed hosting
  • ✅ Tùy chỉnh PHP extensions và config
  • ✅ Hiểu rõ cách hệ thống hoạt động
  • ✅ Không bị giới hạn bởi hosting provider rules

Nhược điểm:

  • ❌ Cần quản lý và maintain server
  • ❌ Trách nhiệm về security và updates
  • ❌ Cần kiến thức về DevOps
  • ❌ Không có managed scaling tự động

Prerequisites

Trước khi bắt đầu, bạn cần chuẩn bị:

1. Domain Name

Mua một domain name và chuẩn bị cấu hình DNS:

  • Tạo A record trỏ đến IP của VPS
  • Thời gian propagate: thường 5-30 phút, có thể đến 48 giờ

2. VPS Server

Yêu cầu tối thiểu cho PHP app:

  • RAM: 1GB+ (khuyến nghị 2GB+)
  • CPU: 1 core+ (khuyến nghị 2 cores)
  • Storage: 20GB+ (khuyến nghị 40GB+)
  • OS: Ubuntu 20.04 LTS hoặc mới hơn

Nơi mua VPS:

  • DigitalOcean (Droplets)
  • AWS EC2
  • Azure VMs
  • Linode
  • Vultr

3. Kiến thức cơ bản

  • SSH và command line
  • Git basics
  • Hiểu cơ bản về Docker
  • Quen thuộc với PHP

Kiến trúc Deployment

Kiến trúc Deployment PHP

Luồng request:

User → Domain → DNS → Nginx (Port 80/443)
                        ↓
                   Docker Network
                        ↓
              ┌─────────┴─────────┐
              ↓                   ↓
        PHP-FPM Container   MySQL Container
        (Port 9000)         (Port 3306)

Các thành phần:

  1. Nginx: Reverse proxy, xử lý SSL, route requests đến PHP-FPM
  2. PHP-FPM Container: Chạy PHP application với PHP-FPM
  3. MySQL Container: Database server
  4. Docker Network: Kết nối các containers

Setup Server

1. SSH vào Server

ssh root@your_server_ip
# hoặc nếu dùng non-root user
ssh user@your_server_ip

2. Update hệ thống

sudo apt update && sudo apt upgrade -y

3. Cài đặt các packages cơ bản

sudo apt install -y curl wget git build-essential

Cài đặt Docker và Docker Compose

Cài đặt Docker

# Remove old versions
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

# Install latest version
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Start Docker service
sudo systemctl start docker
sudo systemctl enable docker

# Verify installation
docker --version

Thêm user vào docker group (optional)

sudo usermod -aG docker $USER
newgrp docker

Setup Nginx

Phần này hướng dẫn cài đặt và cấu hình Nginx trên Ubuntu 20.04, dựa trên hướng dẫn chính thức của DigitalOcean.

Cài đặt Nginx

# Update package index
sudo apt update

# Install Nginx
sudo apt install nginx

# Verify installation
nginx -v

Điều chỉnh Firewall

Trước khi kiểm tra Nginx, bạn cần điều chỉnh firewall để cho phép traffic:

# Check firewall status
sudo ufw status

# Nếu firewall chưa active, enable nó
sudo ufw enable

# Allow OpenSSH (quan trọng - nếu không bạn có thể bị lock out)
sudo ufw allow 'OpenSSH'

# Allow Nginx traffic
sudo ufw allow 'Nginx Full'

# Verify firewall rules
sudo ufw status

WARNING

Quan trọng: Luôn cho phép OpenSSH trước khi enable firewall, nếu không bạn có thể bị lock out khỏi server!

Kiểm tra Web Server

# Check Nginx status
sudo systemctl status nginx

# Nếu chưa chạy, start Nginx
sudo systemctl start nginx

# Enable Nginx để tự động start khi reboot
sudo systemctl enable nginx

Cấu hình Server Blocks cho PHP

# Tạo file config cho domain của bạn
sudo nano /etc/nginx/sites-available/your-domain.com

Nội dung file config cho PHP với PHP-FPM:

server {
    listen 80;
    listen [::]:80;
    
    server_name your-domain.com www.your-domain.com;
    
    root /var/www/html;
    index index.php index.html index.htm;
    
    # Logging
    access_log /var/log/nginx/your-domain.access.log;
    error_log /var/log/nginx/your-domain.error.log;
    
    location / {
        try_files $uri $uri/ =404;
    }
    
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass php-fpm:9000;  # PHP-FPM container
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
    
    location ~ /\.ht {
        deny all;
    }
}

Cấu hình PHP Application

1. Cấu trúc Project

Từ thư mục examples View on GitHub, project có cấu trúc:

php-simple-app/
├── src/                    # PHP source code
│   ├── index.php          # Main entry point
│   ├── config.php         # Configuration
│   └── db.php             # Database connection
├── public/                # Public files (assets)
├── Dockerfile             # PHP-FPM container definition
├── docker-compose.yml     # Multi-container setup
├── nginx.conf             # Nginx configuration
├── deploy.sh              # Deployment script
└── .env.example           # Environment variables example

2. Dockerfile cho PHP-FPM

FROM php:8.2-fpm-alpine

# Install system dependencies
RUN apk add --no-cache \
    git \
    curl \
    libpng-dev \
    libzip-dev \
    zip \
    unzip \
    mysql-client

# Install PHP extensions
RUN docker-php-ext-install pdo_mysql mysqli zip gd

# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Set working directory
WORKDIR /var/www/html

# Copy application files
COPY src/ /var/www/html/
COPY public/ /var/www/html/public/

# Set permissions
RUN chown -R www-data:www-data /var/www/html
RUN chmod -R 755 /var/www/html

# Expose PHP-FPM port
EXPOSE 9000

CMD ["php-fpm"]

3. docker-compose.yml

services:
  nginx:
    image: nginx:alpine
    ports:
      - '80:80'
      - '443:443'
    volumes:
      - ./src:/var/www/html:ro
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - php-fpm
    networks:
      - app_network

  php-fpm:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - ./src:/var/www/html
      - ./public:/var/www/html/public
    environment:
      - DB_HOST=mysql
      - DB_NAME=${DB_NAME}
      - DB_USER=${DB_USER}
      - DB_PASSWORD=${DB_PASSWORD}
    depends_on:
      - mysql
    networks:
      - app_network

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql
    ports:
      - '3306:3306'
    networks:
      - app_network

volumes:
  mysql_data:

networks:
  app_network:
    driver: bridge

4. Ví dụ PHP Application (index.php)

<?php
require_once 'config.php';
require_once 'db.php';

$db = new Database();
$conn = $db->getConnection();

// Example query
$stmt = $conn->prepare("SELECT * FROM users LIMIT 10");
$stmt->execute();
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);

?>
<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PHP Simple App</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
            background: #f5f5f5;
        }
        .container {
            background: white;
            padding: 30px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        h1 {
            color: #333;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 20px;
        }
        th, td {
            padding: 12px;
            text-align: left;
            border-bottom: 1px solid #ddd;
        }
        th {
            background-color: #4CAF50;
            color: white;
        }
        .info {
            background: #e3f2fd;
            padding: 15px;
            border-radius: 4px;
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🚀 PHP Application trên VPS</h1>
        
        <div class="info">
            <strong>Server Info:</strong><br>
            PHP Version: <?php echo phpversion(); ?><br>
            Server: <?php echo $_SERVER['SERVER_SOFTWARE']; ?><br>
            Document Root: <?php echo $_SERVER['DOCUMENT_ROOT']; ?>
        </div>

        <h2>Database Connection: ✅ Connected</h2>
        
        <table>
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Name</th>
                    <th>Email</th>
                    <th>Created At</th>
                </tr>
            </thead>
            <tbody>
                <?php if (empty($users)): ?>
                    <tr>
                        <td colspan="4">No users found. Run database migrations first.</td>
                    </tr>
                <?php else: ?>
                    <?php foreach ($users as $user): ?>
                        <tr>
                            <td><?php echo htmlspecialchars($user['id']); ?></td>
                            <td><?php echo htmlspecialchars($user['name']); ?></td>
                            <td><?php echo htmlspecialchars($user['email']); ?></td>
                            <td><?php echo htmlspecialchars($user['created_at']); ?></td>
                        </tr>
                    <?php endforeach; ?>
                <?php endif; ?>
            </tbody>
        </table>
    </div>
</body>
</html>

Cấu hình MySQL

Database Setup

Sau khi containers chạy, setup database:

# Vào MySQL container
docker exec -it php-simple-app-mysql-1 mysql -u root -p

# Tạo database và user (nếu chưa có)
CREATE DATABASE IF NOT EXISTS myapp_db;
CREATE USER IF NOT EXISTS 'myuser'@'%' IDENTIFIED BY 'mypassword';
GRANT ALL PRIVILEGES ON myapp_db.* TO 'myuser'@'%';
FLUSH PRIVILEGES;
EXIT;

Tạo Tables

# Vào PHP container
docker exec -it php-simple-app-php-fpm-1 sh

# Hoặc import SQL file
docker exec -i php-simple-app-mysql-1 mysql -u myuser -pmypassword myapp_db < schema.sql

Ví dụ schema.sql:

CREATE TABLE IF NOT EXISTS users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO users (name, email) VALUES
('John Doe', 'john@example.com'),
('Jane Smith', 'jane@example.com'),
('Bob Johnson', 'bob@example.com');

Deploy Script

Deploy script tự động hóa toàn bộ quá trình deployment:

Các bước script thực hiện:

BướcCông việcChi tiết
1. Setup PackagesCài đặt dependenciescurl, git, Docker, Docker Compose, Nginx
2. Clone RepositoryDownload source codeClone từ Git hoặc copy local files
3. Generate SSLTạo SSL certificateSử dụng Certbot (Let's Encrypt)
4. Build ApplicationBuild PHP-FPM containerDocker build với Dockerfile
5. Configure NginxSetup reverse proxyCấu hình HTTPS, PHP-FPM integration
6. Setup DatabaseInitialize MySQLTạo database và tables
7. Start ServicesKhởi động containersdocker-compose up -d
8. Setup CronAutomated tasksSSL renewal, backups

Sử dụng Deploy Script

# Download script
curl -o ~/deploy-php.sh https://raw.githubusercontent.com/xirothedev/blog-tech/main/deploy-php.sh

# Chỉnh sửa các biến
nano ~/deploy-php.sh
# Thay đổi: EMAIL, DOMAIN_NAME, DB credentials

# Chạy script
chmod +x ~/deploy-php.sh
./deploy-php.sh

Ví dụ deploy.sh

#!/bin/bash

set -e

# Configuration
EMAIL="your-email@example.com"
DOMAIN="your-domain.com"
PROJECT_DIR="/opt/php-app"

echo "Starting PHP deployment..."

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

# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# Install Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# Install Nginx
sudo apt install -y nginx

# Copy project files
if [ -d "$PROJECT_DIR" ]; then
    cd $PROJECT_DIR && git pull
else
    git clone https://github.com/xirothedev/blog-tech/php-app.git $PROJECT_DIR
    cd $PROJECT_DIR
fi

# Generate SSL certificate
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d $DOMAIN --non-interactive --agree-tos -m $EMAIL

# Build and start containers
cd $PROJECT_DIR
sudo docker-compose build
sudo docker-compose up -d

echo "Deployment completed!"

SSL Certificate với Let's Encrypt

Cài đặt Certbot

sudo apt install -y certbot python3-certbot-nginx

Generate SSL Certificate

# Automatic configuration với Nginx
sudo certbot --nginx -d your-domain.com

# Hoặc chỉ generate certificate
sudo certbot certonly --nginx -d your-domain.com

Auto-renewal

Certbot tự động setup cron job để renew certificate:

# Test renewal
sudo certbot renew --dry-run

# Check renewal status
sudo systemctl status certbot.timer

Rate Limiting và Security

Nginx Rate Limiting

# /etc/nginx/nginx.conf
http {
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
    
    server {
        location / {
            limit_req zone=api_limit burst=20 nodelay;
            try_files $uri $uri/ /index.php?$query_string;
        }
    }
}

Security Headers

server {
    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    
    # Hide PHP version
    fastcgi_hide_header X-Powered-By;
}

PHP Security Settings

Trong php.ini:

; Disable dangerous functions
disable_functions = exec,passthru,shell_exec,system,proc_open,popen

; Hide PHP version
expose_php = Off

; Error reporting (production)
display_errors = Off
log_errors = On

Update và Maintenance

Maintenance Commands

Mục đíchCommandMô tả
Check containersdocker-compose psXem trạng thái containers
View PHP logsdocker-compose logs php-fpmXem logs của PHP-FPM
View Nginx logsdocker-compose logs nginxXem logs của Nginx
View MySQL logsdocker-compose logs mysqlXem logs của MySQL
Restart servicesdocker-compose restartKhởi động lại tất cả services
Stop servicesdocker-compose downDừng và xóa containers
Start servicesdocker-compose up -dKhởi động containers trong background
Enter PHP containerdocker exec -it php-fpm-1 shVào trong PHP container
Enter MySQL containerdocker exec -it mysql-1 mysql -u root -pVào MySQL CLI

Backup Strategy

Loại BackupTần suấtCách thực hiện
Database BackupDailymysqldump hoặc Docker volume backup
Application CodePer deploymentGit repository
Environment VariablesOn changesSecure storage
Docker VolumesWeeklyVolume backup scripts

Backup Database

# Backup MySQL
docker exec php-simple-app-mysql-1 mysqldump -u myuser -pmypassword myapp_db > backup_$(date +%Y%m%d).sql

# Restore from backup
docker exec -i php-simple-app-mysql-1 mysql -u myuser -pmypassword myapp_db < backup_20250101.sql

Troubleshooting

Các vấn đề thường gặp

Vấn đềNguyên nhân có thểGiải pháp
502 Bad GatewayPHP-FPM không chạyKiểm tra docker-compose ps và logs
Database connection errorConnection string saiKiểm tra DB credentials trong .env
Nginx 404File paths không đúngKiểm tra root và index settings
PHP errors không hiệndisplay_errors = OffKiểm tra php.ini hoặc logs
SSL certificate failDNS chưa propagateĐợi DNS update

Debug Commands

# Check Docker containers
docker ps -a

# Check Docker logs
docker-compose logs --tail=100 php-fpm
docker-compose logs --tail=100 nginx

# Check Nginx config
sudo nginx -t

# Check PHP-FPM status
docker exec php-fpm-1 php-fpm -v

# Test database connection
docker exec mysql-1 mysql -u myuser -pmypassword -e "SHOW DATABASES;"

Best Practices

1. Environment Variables

BiếnMô tảBảo mật
DB_HOSTMySQL host✅ Không commit vào Git
DB_NAMEDatabase name⚠️ Có thể commit nếu không sensitive
DB_USERDatabase user✅ Không commit
DB_PASSWORDDatabase password✅ Không commit
APP_ENVEnvironment (prod/dev)✅ Set trong Docker

2. PHP Best Practices

PracticeBenefitImplementation
Use prepared statementsPrevent SQL injectionPDO or mysqli
Error handlingBetter debuggingtry-catch blocks
Input validationSecurityFilter input, validate
Cache opcachePerformanceEnable OPcache
Composer autoloadBetter code organizationUse PSR-4 autoloading

3. Docker Best Practices

PracticeLý doImplementation
Multi-stage buildsGiảm image sizeSeparate build và runtime
Non-root userSecurityRun với www-data user
.dockerignoreFaster buildsExclude vendor, .git
Health checksAuto-recoveryHealthcheck trong docker-compose
Resource limitsPrevent exhaustionSet CPU/memory limits

4. Security Checklist

  • ✅ Keep system updated
  • ✅ Use strong passwords
  • ✅ Enable firewall (UFW)
  • ✅ SSH key authentication
  • ✅ Regular security audits
  • ✅ Monitor logs
  • ✅ Use HTTPS only
  • ✅ Security headers trong Nginx
  • ✅ Rate limiting
  • ✅ Database access restrictions
  • ✅ Hide PHP version
  • ✅ Disable dangerous PHP functions

Kết luận

Deploy PHP lên VPS là một quá trình có thể phức tạp nhưng mang lại nhiều lợi ích: kiểm soát hoàn toàn, chi phí linh hoạt và hiệu năng tốt. Với Docker và Nginx, bạn có thể setup một production-ready deployment.

Điểm mấu chốt

  • ✅ Docker simplifies deployment - Containerization giúp dễ quản lý
  • ✅ Nginx + PHP-FPM - Hiệu năng tốt cho PHP applications
  • ✅ MySQL for data - Reliable database solution
  • ✅ SSL/HTTPS - Bảo mật cho production
  • ✅ Automation is key - Deploy scripts giúp tiết kiệm thời gian

Tài nguyên tham khảo

  • 📦 PHP Simple App Example - Source code mẫu View on GitHub
  • 📚 PHP Documentation
  • 🐳 Docker Documentation
  • 🌐 Nginx Documentation
  • 📘 MySQL Documentation

Bước tiếp theo

  1. Clone example project: Thử nghiệm với php-simple-app
  2. Customize: Tùy chỉnh theo nhu cầu của bạn
  3. Deploy: Chạy deploy script trên VPS
  4. Monitor: Setup monitoring và alerting
  5. Optimize: Tối ưu performance

TIP

Bắt đầu với example: Sử dụng php-simple-app như một starting point để hiểu cách setup và customize cho project của bạn!

NOTE

  • PHP Official Documentation
  • Docker PHP Images
  • Nginx PHP-FPM Configuration
View on GitHub

Tags

phpdeploymentvpsdockernginxself-hosting

Previous Article

DDoS Attack: Tấn công DDoS là gì và cách phòng chống

Next Article

Hướng dẫn Deploy Next.js lên VPS: Self-hosting với Docker và Nginx
← Back to the blog
mailMailgithubGitHubfacebookFacebooklinkedinLinkedin
Xiro The Dev
•
© 2025
•
Home