Deploy an LNMP stack

更新时间:
复制 MD 格式

Install Nginx, MySQL, and PHP on an ECS instance with full control over configuration and security hardening for production websites.

Solution architecture

image
  1. A user's browser sends an HTTP request to port 80 of the ECS instance.

  2. Nginx receives and routes requests. It serves static assets such as HTML, CSS, and images directly, and forwards PHP requests (files ending in .php) to PHP-FPM via FastCGI.

  3. PHP-FPM executes the PHP code. If the code needs database access, it connects to MySQL.

  4. MySQL executes SQL queries from the PHP script and returns the results.

  5. PHP-FPM generates HTML from the result and returns it to Nginx, which sends the response to the browser.

Procedure

We recommend an ECS instance with at least 2 GiB of memory.

Step 1: Prepare the instance

Configure public access and security group rules, then update system packages.

  1. Configure public access.

    Ensure your ECS instance has a public IP address or an Elastic IP Address (EIP). See Enable public access for an instance.

  2. Add a security group rule.

    In the security group of your ECS instance, add an inbound rule to allow TCP traffic on port 80. See Add a security group rule.

  3. Update system packages.

    Alibaba Cloud Linux 3 / CentOS 8

    1. Log on to the ECS instance.

      1. Go to ECS console - Instances. In the top-left corner, select the region and resource group for the target resource.

      2. On the instance details page, click Connect and select Workbench. Follow the on-screen prompts to log on.

    2. Update all packages.

      sudo dnf update -y

    Alibaba Cloud Linux 2 / CentOS 7

    1. Log on to the ECS instance.

      1. Go to ECS console - Instances. In the top-left corner, select the region and resource group for the target resource.

      2. On the instance details page, click Connect and select Workbench. Follow the on-screen prompts to log on.

    2. Update all packages.

      sudo yum update -y

    Ubuntu

    1. Log on to the ECS instance.

      1. Go to ECS console - Instances. In the top-left corner, select the region and resource group for the target resource.

      2. On the instance details page, click Connect and select Workbench. Follow the on-screen prompts to log on.

    2. Update all packages.

      sudo apt update -y && sudo apt upgrade -y

Step 2: Install Nginx

  1. Add the Nginx repository and install Nginx.

    Alibaba Cloud Linux 3 / CentOS 8

    # Add the official Nginx repository.
    sudo tee /etc/yum.repos.d/nginx.repo <<-'EOF'
    [nginx-stable]
    name=nginx stable repo
    baseurl=https://nginx.org/packages/centos/8/$basearch/
    gpgcheck=1
    enabled=1
    gpgkey=https://nginx.org/keys/nginx_signing.key
    module_hotfixes=true
    EOF
    
    # Install Nginx.
    sudo dnf -y install nginx

    Alibaba Cloud Linux 2 / CentOS 7

    # Add the official Nginx repository.
    sudo tee /etc/yum.repos.d/nginx.repo <<-'EOF'
    [nginx-stable]
    name=nginx stable repo
    baseurl=https://nginx.org/packages/centos/7/$basearch/
    gpgcheck=1
    enabled=1
    gpgkey=https://nginx.org/keys/nginx_signing.key
    module_hotfixes=true
    EOF
    
    # Install Nginx.
    sudo yum -y install nginx

    Ubuntu

    # Install dependencies for Nginx.
    sudo apt install -y curl gnupg2 ca-certificates lsb-release ubuntu-keyring
    
    # Import the official Nginx signing key to verify package authenticity.
    curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null
    
    # Set up the APT repository for Nginx.
    echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" | sudo tee /etc/apt/sources.list.d/nginx.list
    
    # Install Nginx.
    sudo apt update
    sudo apt install -y nginx
  2. Start Nginx and enable it on boot.

    The enable --now flag starts the service and enables it on boot.
    sudo systemctl enable --now nginx
  3. Verify that Nginx is running.
    Run curl http://127.0.0.1. If the output contains the Nginx welcome page HTML, the installation succeeded.

Step 3: Install MySQL

  1. Install MySQL.

    Alibaba Cloud Linux 3 / CentOS 8

    # Alibaba Cloud Linux 3 requires compat-openssl10 for compatibility with older OpenSSL versions.
    if [ -f /etc/os-release ]; then
        . /etc/os-release
        if [ "$ID" = "alinux" ] && [ "$VERSION_ID" = "3" ]; then
            sudo yum install -y compat-openssl10
        fi
    fi
    
    # Add the YUM repository for MySQL 8.4.
    sudo rpm -Uvh https://repo.mysql.com/mysql84-community-release-el8-1.noarch.rpm
    
    # Install the MySQL service.
    sudo dnf install -y mysql-server

    Alibaba Cloud Linux 2 / CentOS 7

    # Add the repository for MySQL 8.4.
    sudo rpm -Uvh https://repo.mysql.com/mysql84-community-release-el7-1.noarch.rpm
    
    # Install the MySQL service.
    sudo yum install -y mysql-server

    Ubuntu

    # The default Ubuntu repository includes MySQL, so you can install it directly.
    # Ubuntu 20.04 and later versions install MySQL 8.0 by default.
    # Ubuntu 18.04 and earlier versions install MySQL 5.x by default.
    sudo apt install -y mysql-server
  2. Start MySQL and enable it on boot.

    Alibaba Cloud Linux / CentOS

    sudo systemctl enable --now mysqld

    Ubuntu

    sudo systemctl enable --now mysql
  3. Perform security hardening.

    The default MySQL configuration allows anonymous users and remote root logon. Run the security script to fix these vulnerabilities.

    1. Get the initial root password.

      This step applies only to Alibaba Cloud Linux and CentOS. Ubuntu uses socket-based authentication for root by default.

      # Extract and display the temporary password from the log.
      sudo grep 'temporary password' /var/log/mysqld.log
    2. Run the security script.

      Run sudo mysql_secure_installation and follow the prompts:

      1. Reset the root password: Use the temporary password to log on and set a new password.

        Use a strong password of at least 12 characters with uppercase, lowercase, numbers, and special characters.
      2. Remove anonymous users: Enter Y.

      3. Disallow remote root logon: Enter Y.

      4. Remove the test database: Enter Y.

      5. Reload the privilege tables: Enter Y.

Step 4: Install PHP

Install PHP, PHP-FPM, and the MySQL extension.

  1. Add a PHP repository and install PHP.

    Alibaba Cloud Linux 3 / CentOS 8

    # Add the Remi repository.
    cat > /etc/yum.repos.d/remi.repo << 'EOF'
    [remi]
    name=Remi\'s RPM Repository for Enterprise Linux 8 - x86_64
    baseurl=https://rpms.remirepo.net/enterprise/8/remi/x86_64/
    enabled=1
    gpgcheck=1
    gpgkey=https://rpms.remirepo.net/RPM-GPG-KEY-remi2024
    
    [remi-safe]
    name=Remi Safe Repository
    baseurl=https://rpms.remirepo.net/enterprise/8/safe/x86_64/
    enabled=1
    gpgcheck=1
    gpgkey=https://rpms.remirepo.net/RPM-GPG-KEY-remi2024
    EOF
    
    # Import the GPG key.
    rpm --import https://rpms.remirepo.net/RPM-GPG-KEY-remi2024
    
    # Install yum-utils.
    dnf install -y yum-utils
    
    # Install PHP, PHP-FPM, and the MySQL extension.
    dnf install -y php php-fpm php-mysqlnd

    Alibaba Cloud Linux 2 / CentOS 7

    # Install the Remi repository.
    sudo yum install -y http://rpms.remirepo.net/enterprise/remi-release-7.rpm
    
    # Install yum-utils and enable the PHP 8.2 repository.
    sudo yum install -y yum-utils
    sudo yum-config-manager --enable remi-php82
    
    # Install PHP, PHP-FPM, and the MySQL extension.
    sudo yum install -y php php-fpm php-mysqlnd

    Ubuntu

    # Install the tool for managing PPAs.
    sudo apt install -y software-properties-common
    
    # Add the ondrej/php PPA repository.
    sudo add-apt-repository -y ppa:ondrej/php
    
    # Install PHP 8.2, PHP-FPM, and the MySQL extension.
    sudo apt install -y php8.2 php8.2-fpm php8.2-mysql
  2. Start PHP-FPM and enable it on boot.

    Alibaba Cloud Linux / CentOS

    sudo systemctl enable --now php-fpm

    Ubuntu

    sudo systemctl enable --now php8.2-fpm

Step 5: Configure Nginx to process PHP

By default, Nginx serves only static files. Configure it to forward .php requests to PHP-FPM. Without this, PHP pages will be downloaded or trigger errors.

  1. Back up the default Nginx configuration file.

    sudo cp /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bak
  2. Get the communication address for the PHP-FPM service.

    Nginx uses this address to forward PHP requests to PHP-FPM.

    Alibaba Cloud Linux / CentOS

    PHP_FPM_LISTEN=$(sudo sed -n 's/^\s*listen\s*=\s*//p' /etc/php-fpm.d/www.conf | head -n 1)
    echo "$PHP_FPM_LISTEN"

    Ubuntu

    PHP_FPM_LISTEN=$(sudo sed -n 's/^\s*listen\s*=\s*//p' /etc/php/8.2/fpm/pool.d/www.conf | head -n 1)
    echo "$PHP_FPM_LISTEN"
  3. Configure Nginx to forward .php requests to PHP-FPM.

    Replace <PHP_FPM_ADDRESS> with the communication address from the previous step and run the command.

    • If the output is a file path (for example, /run/php-fpm/www.sock), the service uses a Unix socket. The address format is unix:FILE_PATH, such as unix:/run/php-fpm/www.sock.

    • If the output is an IP address and port (for example, 127.0.0.1:9000), the service uses a TCP socket. Use the IP address and port directly as the communication address.

    sudo tee /etc/nginx/conf.d/default.conf <<-'EOF'
    server {
        listen       80;
        server_name  localhost;
        root /usr/share/nginx/html;
    
        # Set the default index files.
        index index.php index.html index.htm;
    
        location / {
            try_files $uri $uri/ /index.php?$query_string;
        }
    
        # Forward .php requests to PHP-FPM.
        location ~ \.php$ {
            fastcgi_pass   <PHP_FPM_ADDRESS>;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include        fastcgi_params;
        }
    
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }
    }
    EOF
  4. Test the configuration file.

    sudo nginx -t
    • If the output contains successful, the configuration is correct. Proceed to the next step.

    • If the output contains failed, verify that you replaced the address correctly, or restore the backup:

      sudo mv /etc/nginx/conf.d/default.conf.bak /etc/nginx/conf.d/default.conf
  5. Restart Nginx to apply the new configuration.

    sudo systemctl restart nginx

Step 6: Verify the stack

Create test files to verify that Nginx processes PHP and PHP connects to MySQL.

  1. Verify Nginx processing of PHP.

    1. Create a test file: Generate a file with phpinfo() to display the PHP configuration.

      echo "<?php phpinfo(); ?>" | sudo tee /usr/share/nginx/html/phpinfo.php
    2. Verify in a browser: Navigate to http://<ECS_PUBLIC_IP>/phpinfo.php.

      • Success: The page displays PHP environment details.

      • Failure: The page displays "File not found" or the browser downloads the file.

    3. Clean up the test file: Delete this file after verification. The phpinfo page exposes sensitive server information.

  2. Verify the PHP-to-MySQL connectivity.

    Create a PHP script to test database connectivity with the mysqlnd extension.

    1. Create a database and a user.
      Log in to MySQL with the root password set in Step 3.

      sudo mysql -u root -p

      At the mysql> prompt, create a database named webapp, a user named webuser, and grant the user all privileges on that database.

      CREATE DATABASE webapp;
      /* Replace <YourPassword> with a strong password. */
      CREATE USER 'webuser'@'localhost' IDENTIFIED BY '<YourPassword>';
      GRANT ALL PRIVILEGES ON webapp.* TO 'webuser'@'localhost';
      FLUSH PRIVILEGES;
      EXIT;
    2. Create a database connection test file.
      Replace <YourPassword> with the user password from the previous step and run the command.

      sudo tee /usr/share/nginx/html/test.php <<-'EOF'
      <?php
      $servername = "localhost";
      $username = "webuser";
      $password = "<YourPassword>";
      $dbname = "webapp";
      // Create a connection
      $conn = new mysqli($servername, $username, $password, $dbname);
      // Check the connection
      if ($conn->connect_error) {
          die("Database connection failed: " . $conn->connect_error);
      }
      echo "Database connection successful!";
      ?>
      EOF
      • Verify in a browser: Navigate to http://<ECS_PUBLIC_IP>/test.php. If the page displays Database connection successful!, the connection works.

      • Clean up the test file: This script contains database credentials in plain text. Delete it immediately after testing.

FAQ

Browser timeout or "site can't be reached"

This error usually indicates a network issue. Troubleshoot in the following order:

  1. Security group: Verify that the security group allows inbound traffic on port 80.

  2. Firewall: Verify that the OS firewall (such as firewalld or ufw) allows traffic on port 80.

  3. Nginx service: Run sudo systemctl status nginx to check if Nginx is running. If not, check the logs with sudo journalctl -xeu nginx.

  4. Port conflict: Check whether port 80 is occupied by another application. See Troubleshoot port connectivity issues for an ECS instance.

Browser displays "502 Bad Gateway"

This error means Nginx cannot communicate with PHP-FPM.

  1. PHP-FPM service: Run sudo systemctl status php-fpm (or php8.2-fpm on Ubuntu) to check if the service is running.

  2. Socket path: Verify that the unix: path in the fastcgi_pass directive matches the listen path in the PHP-FPM configuration.

  3. SELinux/AppArmor: On CentOS or Alibaba Cloud Linux, SELinux may block Nginx-to-PHP-FPM communication. Run sestatus and check /var/log/audit/audit.log for denials.

  4. Socket permissions: Verify that the Nginx user has read/write access to the socket file.

Enable remote access to MySQL

By default, MySQL prohibits remote logon. To enable it, create a dedicated remote-access user instead of using root. See Add a user for remote MySQL access.