This topic walks you through building a LNMP (Linux, Nginx, MySQL, PHP) stack on an Elastic Compute Service (ECS) instance using a package manager. It supports multiple operating systems, including Alibaba Cloud Linux, CentOS, and Ubuntu. By deploying manually, you can overcome the limitations of pre-built images, which often complicate the customization of configuration files. This approach gives you full control over the underlying environment and enables security hardening to meet the production requirements for dynamic websites like WordPress and Magento.
Solution architecture
|
Procedure
For a smooth experience, we recommend an ECS instance with at least 2 GiB of memory.
Step 1: Prepare the instance
Configure a public IP address and security group to ensure the instance is accessible from the internet, and update system components to patch potential security vulnerabilities.
Configure public network access.
Ensure your ECS instance has a public IP address assigned or an Elastic IP Address (EIP) bound to it. For detailed instructions, see Configure public bandwidth.
Add a security group rule.
In the security group associated with your ECS instance, add an inbound rule to allow traffic on TCP Port 80 (for HTTP access). For detailed instructions, see Add a security group rule.
Update the system and software packages.
Alibaba Cloud Linux 3 / CentOS 8
Log on to the ECS instance.
Go to ECS console - Instances. In the top navigation bar, select the target region and resource group.
Go to the instance details page. Click Connect and select Workbench. Follow the on-screen prompts to log on and open the terminal.
Update the package index and upgrade all packages to their latest versions.
sudo dnf update -y
Alibaba Cloud Linux 2 / CentOS 7
Log on to the ECS instance.
Go to ECS console - Instances. In the top navigation bar, select the target region and resource group.
On the instance details page, click Connect and select Workbench. Follow the on-screen prompts to log on and open the terminal.
Update all packages to their latest versions.
sudo yum update -y
Ubuntu
Log on to the ECS instance.
Go to ECS console - Instances. In the top navigation bar, select the target region and resource group.
On the instance details page, click Connect and select Workbench. Follow the on-screen prompts to log on and open the terminal.
Update all packages to their latest versions.
sudo apt update -y && sudo apt upgrade -y
Step 2: Install and configure Nginx
Add the official 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 nginxAlibaba 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 nginxUbuntu
# Install Nginx dependencies. sudo apt install -y curl gnupg2 ca-certificates lsb-release ubuntu-keyring # Import the official Nginx signature key to verify the package source. 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 nginxStart Nginx and enable it to start on boot.
The
enable --nowflag both starts the service immediately and enables it to start on boot.sudo systemctl enable --now nginxVerify the Nginx service.
Runcurl http://127.0.0.1. If the terminal outputs the HTML code for the Nginx welcome page, the installation was successful.
Step 3: Install and harden MySQL
Install and start 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-serverAlibaba 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-serverUbuntu
# 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-serverStart MySQL and enable it to start on boot.
Alibaba Cloud Linux / CentOS
sudo systemctl enable --now mysqldUbuntu
sudo systemctl enable --now mysqlPerform security hardening.
MySQL's default configuration is insecure, allowing anonymous users and remote root logon. Run the included security script to address these vulnerabilities.
Get the initial root password.
This step only applies to Alibaba Cloud Linux/CentOS, as Ubuntu uses passwordless authentication by default. Use this temporary password in the next step to reset the root password.
# Extract and display the temporary password from the log. sudo grep 'temporary password' /var/log/mysqld.logRun the hardening script.
Run the command
sudo mysql_secure_installationand follow the prompts to complete the recommended configurations:Reset the root password: Use the temporary password from the previous step to log on and set a new password.
We recommend setting a strong password that is at least 12 characters long and includes uppercase and lowercase letters, numbers, and special symbols.
Remove anonymous users: Enter Y.
Disallow remote root logon: Enter Y.
Remove the test database: Enter Y.
Reload the privilege tables: Enter Y.
Step 4: Install and configure PHP
Install PHP, PHP-FPM (for handling web requests), and the MySQL driver.
Add the PHP repository and install PHP.
Alibaba Cloud Linux 3 / CentOS 8
# Add the PHP 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 tools and enable the module. dnf install -y yum-utils # Install PHP. dnf install -y php php-fpm php-mysqlndAlibaba 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 PHP 8.2. 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-mysqlndUbuntu
# Install the PPA management tool. 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-mysqlStart PHP-FPM and enable it to start on boot.
Alibaba Cloud Linux / CentOS
sudo systemctl enable --now php-fpmUbuntu
sudo systemctl enable --now php8.2-fpm
Step 5: Configure Nginx to support PHP processing
By default, Nginx can only serve static web pages (like HTML and images). To support dynamic websites like WordPress, you must configure Nginx to forward .php file requests to PHP-FPM for processing. Otherwise, when you try to access a PHP page, the file may be downloaded directly or fail to display.
Back up the Nginx configuration file.
sudo cp /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bakGet the communication address for the PHP service.
Nginx uses this address to forward PHP requests to PHP-FPM for processing.
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"Configure Nginx to forward
.phprequests to the PHP service.Based on the output from the previous step, replace
<PHP_COMMUNICATION_ADDRESS>in the command below with the actual communication address, and then run the command:If the output is a file path (for example,
/run/php-fpm/www.sock): The communication method is a Unix socket (for local communication only, offering better performance and security). The address format isunix:FILE_PATH, such asunix:/run/php-fpm/www.sock.If the output is an IP address and port (for example,
127.0.0.1:9000): The communication method is TCP (supports cross-host communication, suitable for scenarios where PHP-FPM and Nginx are deployed on separate hosts). You can use this 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 file. 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-communication-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; } } EOFCheck the configuration file.
sudo nginx -tIf the output contains the keyword successful, the configuration is correct. You can proceed to the next step.
If the output contains failed, the configuration is incorrect. Check if you replaced the path in the command correctly, or run the following command to restore the configuration from the backup:
sudo mv /etc/nginx/conf.d/default.conf.bak /etc/nginx/conf.d/default.conf
Restart Nginx to apply the new configuration.
sudo systemctl restart nginx
Step 6: Verify the LNMP stack
Create test files to verify that Nginx can process PHP and that PHP can connect to the MySQL database. This verifies that all components of the LNMP stack are working together correctly.
Verify Nginx processing.
Create a test file: Generate a file containing the
phpinfo()function to display the detailed configuration of the PHP environment.echo "<?php phpinfo(); ?>" | sudo tee /usr/share/nginx/html/phpinfo.phpVerify in a browser: Visit
http://<ECS-public-IP-address>/phpinfo.php.Success: The page renders correctly and displays detailed information about the PHP environment.
Failure: The page displays
File not foundor the file is downloaded directly.
Clean up the test file: After successful verification, you must delete this file. The
phpinfopage contains sensitive information that poses a security risk if exposed to the public.
Verify the connection between PHP and MySQL.
Create a PHP script to test if PHP can connect to the database using the mysqlnd extension.
Create a database and user.
Log on to MySQL with the root account (enter therootpassword you set in Step 3.sudo mysql -u root -pAt the
mysql>prompt, run the following commands to create a database namedwebappand a user namedwebuser, and grant it 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;Create a database connection test file.
Replace<YourPassword>in the command below with the user password you set in the previous step, and then run the command.sudo tee /usr/share/nginx/html/test.php <<-'EOF' <?php $servername = "localhost"; $username = "webuser"; $password = "<YourPassword>"; $dbname = "webapp"; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Database connection failed: " . $conn->connect_error); } echo "Database connection successful!"; ?> EOFVerify in a browser: Visit http://<ECS-public-IP-address>/test.php. If the page displays Database Connection Successful!, the test is successful.
Clean up the test file: This script contains the database username and password in plain text. You must delete it immediately after testing to prevent credential leakage.
FAQ
The browser times out or displays "This site can't be reached"
This is usually a network connectivity issue. Troubleshoot in the following order:
Security Group: Check if the inbound rules of the instance's security group allow traffic on Port 80.
Firewall: Check if the operating system's internal firewall (such as
firewalldorufw) is disabled or has a rule allowing traffic on Port 80.Nginx service: Run
sudo systemctl status nginxto check if the Nginx service is running. If not, check the logs withsudo journalctl -xeu nginxto diagnose the issue.Port conflict: Check if Port 80 is being used by another program. For detailed troubleshooting steps, see Troubleshoot port connectivity issues for an ECS instance.
The browser displays "502 Bad Gateway"
This error indicates that Nginx cannot communicate properly with PHP-FPM.
PHP-FPM service: Run
sudo systemctl status php-fpm(orphp8.2-fpmfor Ubuntu) to check if the service is running.Socket path: Verify that the path following
unix:in thefastcgi_passdirective in your Nginx configuration file exactly matches thelistenpath in your PHP-FPM configuration file.SELinux/AppArmor: On CentOS/Alibaba Cloud Linux, SELinux policies might be blocking communication between Nginx and PHP-FPM. Run
sestatusto check its status and review the denial logs in/var/log/audit/audit.log.Socket permissions: Check the permissions of the socket file to ensure the user running Nginx has read and write access.
How do I allow remote access to MySQL?
By default, MySQL prohibits remote logon. If you need to enable it, we recommend creating a dedicated user for remote access instead of allowing the root user to connect remotely. For detailed instructions, see Add a user for remote MySQL access.