Manual for Setting Up a LAMP Stack with Nginx, PHP, and MySQL
Prerequisites
- You must have a fresh Ubuntu 24.04 server with root privileges.
- A Fully Qualified Domain Name (FQDN) that points to your server's IP address.
Step 1: SSH into the Server
Connect to your server using SSH. Replace hostname.example.com
with your domain or server IP address and user
with your actual username.
ssh user@hostname.example.com
Step 2: Install Nginx
Nginx is a popular web server software. To ensure you have the latest stable version, install it from a third-party repository maintained by Ondřej Surý.
-
Add the Repository: Add the PPA (Personal Package Archive) containing the latest Nginx packages.
sudo add-apt-repository ppa:ondrej/nginx -y
-
Update the Package Lists: Refresh the list of available packages.
sudo apt update
-
Upgrade Existing Packages: Update and upgrade outdated packages on your server.
sudo apt dist-upgrade -y
-
Install Nginx: Install Nginx.
sudo apt install nginx -y
-
Verify Installation: Confirm that Nginx has been installed successfully.
nginx -v
You can visit your server’s IP address or FQDN in your browser to see the Nginx welcome page.
Step 3: Basic Nginx Configuration
-
Determine CPU Core Count and File Limit: These values help optimize the server configuration for better performance.
grep processor /proc/cpuinfo | wc -l ulimit -n
-
Edit Nginx Configuration: Open the Nginx configuration file to adjust basic settings.
sudo nano /etc/nginx/nginx.conf
While not listing every configuration directive, focus on the following:
-
User Setting: Set the user to the username you're currently logged in with. This simplifies file permission management but is only secure if only one user has access to the server.
-
Worker Processes: The
worker_processes
directive determines how many worker processes to spawn. A general rule is to set this to the number of CPU cores. For example, if you have 1 CPU core, set it to 1. -
Events Block:
- Worker Connections: Set
worker_connections
to match your server’s open file limit. This controls the number of simultaneous connections each worker process can handle. For instance, with two CPU cores and an open file limit of 1024, your server can handle 2048 connections. Keep in mind that the number of connections doesn't directly translate to the number of users due to multiple connections per request. - Multi Accept: Uncomment and set
multi_accept
toon
. This makes each worker process accept all new connections simultaneously rather than one at a time.
- Worker Connections: Set
-
HTTP Block:
- Keepalive Timeout: Add the
keepalive_timeout
directive to specify how long a connection to a client should be kept open before being closed by Nginx. Lower this value to ensure idle connections are not kept open unnecessarily. For example, setting it to 15 seconds. Add this directive just above thesendfile on;
directive.
- Keepalive Timeout: Add the
user user; worker_processes auto; pid /run/nginx.pid; events { worker_connections 768; multi_accept on; } http { keepalive_timeout 15; sendfile on; server_tokens off; client_max_body_size 64m; }
-
-
Restart Nginx: Test the configuration and restart Nginx.
sudo nginx -t sudo service nginx restart
Step 4: Install PHP 8.3
-
Add PHP Repository: Add the PHP repository for the latest version of PHP.
sudo add-apt-repository ppa:ondrej/php -y sudo apt update
-
Install PHP 8.3 and Modules: Install PHP and essential modules.
sudo apt install php8.3-fpm php8.3-common php8.3-mysql \ php8.3-xml php8.3-intl php8.3-curl php8.3-gd \ php8.3-imagick php8.3-cli php8.3-dev php8.3-imap \ php8.3-mbstring php8.3-opcache php8.3-redis \ php8.3-soap php8.3-zip -y
Note:
- You'll see
php-fpm
among the packages being installed. FastCGI Process Manager (FPM) is a PHP FastCGI implementation with extra features that works exceptionally well with Nginx. It’s the preferred process manager when setting up PHP with Nginx. Once the installation is complete, test PHP to ensure it has been installed correctly. For more information, visit PHP FPM Installation (opens in a new tab).
-
Verify PHP Installation: Check the installed PHP version.
php-fpm8.3 -v
Step 5: Configure PHP and PHP-FPM
-
Modify PHP-FPM Configuration: Update the pool configuration file to run PHP-FPM under your user account. by default it is it runs as the www-data user After installing Nginx and PHP, you’ll need to configure the user and group under which the service will operate. This setup does not offer security isolation between sites by configuring separate PHP pools, so we will run a single PHP pool under your user account.
sudo nano /etc/php/8.3/fpm/pool.d/www.conf
Replace the following lines:
user = user group = user listen.owner = user listen.group = user
-
Adjust PHP Settings: Update the
php.ini
file for PHP settings.sudo nano /etc/php/8.3/fpm/php.ini
Adjust these values to match the value you assigned to the client_max_body_size directive when configuring Nginx:
upload_max_filesize = 64M post_max_size = 64M
While we’re editing the
php.ini
file, let's also enable the OPcache file override setting. With this setting enabled, OPcache will serve the cached version of PHP files without checking for modifications on the file system, which enhances PHP performance. look for thefile_override
line we need to modify. Uncomment it (remove the semicolon) and change the value from zero to one:opcache.enable_file_override = 1
-
Restart PHP-FPM: Test and restart PHP-FPM.
sudo php-fpm8.3 -t sudo service php8.3-fpm restart
Run the htop command to confirm Nginx and PHP are running under the correct user. Press SHIFT + M to sort the output by memory usage, which should bring the
php-fpm
processes into view. Scrolling to the bottom will also reveal a fewnginx
processes. You should see one instance of each process running under the root user, which is the main process responsible for spawning worker processes. The rest should be running under the username you specified.htop
Step 6: Configure Nginx to Use PHP
To verify that Nginx and PHP are working together correctly, enable PHP in the default Nginx site configuration and create a PHP info file to view in your browser. While this step is optional, it can be useful for confirming that PHP files are being properly processed by the Nginx web server. Start by uncommenting a section in the default Nginx site configuration that was set up when you installed Nginx:
-
Modify Nginx Site Configuration: Update the Nginx site configuration to handle PHP files.
sudo nano /etc/nginx/sites-available/default
Look for the section which controls the PHP scripts it will look like this:
# pass PHP scripts to FastCGI server # #location ~ \.php$ { # include snippets/fastcgi-php.conf; # # # With php-fpm (or other unix sockets): # fastcgi_pass unix:/run/php/php8.3-fpm.sock; # # With php-cgi (or other tcp sockets): # fastcgi_pass 127.0.0.1:9000; #}
Since we are using php-fpm, we can change that section to look like this:Add the following block to process
.php
files:# pass PHP scripts to FastCGI server location ~ \.php$ { include snippets/fastcgi-php.conf; # With php-fpm (or other unix sockets): fastcgi_pass unix:/run/php/php8.3-fpm.sock; }
Now we can save the file.
-
Test and Restart Nginx: Test the Nginx configuration and restart the service.
sudo nginx -t sudo service nginx restart
-
Create PHP Info File: Create a PHP info file to verify PHP is working correctly with Nginx.
sudo nano /var/www/html/info.php
Add:
<?php phpinfo();
Finally, since you set the user directive in your
nginx.conf
file to the user you’re currently logged in as, make sure that user has permissions for theinfo.php
file.sudo chown user /var/www/html/info.php
-
Verify in Browser: Visit
http://hostname.example.com/info.php
to check the PHP info page. After verification, delete the file:sudo rm /var/www/html/info.php
-
Set Up a Catch-All Server Block
Currently, accessing your server’s domain name in a web browser shows the Nginx welcome page. To improve this, configure the server to return an empty response for domain names not set up in Nginx.
Start by removing the two default site configuration files:
sudo rm /etc/nginx/sites-available/default
sudo rm /etc/nginx/sites-enabled/default
Next, add a catch-all block to the Nginx configuration. Open the nginx.conf
file for editing:
sudo nano /etc/nginx/nginx.conf
Locate the line towards the bottom of the file:
include /etc/nginx/sites-enabled/*;
Add the following block beneath it:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 444;
}
Press CTRL + X and then Y to save your changes. Test the Nginx configuration:
sudo nginx -t
If the test is successful, restart Nginx:
sudo service nginx restart
Your domain should now return an error when accessed.
Step 7: Install WP-CLI
WP-CLI is a command-line tool for managing WordPress.
-
Download WP-CLI: Download WP-CLI.
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
You can then check that it works by using the command:
php wp-cli.phar --info
The command should output information about your current PHP version and other details.
-
Set Permissions and Move to Path: Make WP-CLI executable and move it to a directory in your system’s PATH.
chmod +x wp-cli.phar sudo mv wp-cli.phar /usr/local/bin/wp
-
Verify Installation: Test WP-CLI:
wp --info
-
You are now able to access the WP-CLI tool using the
wp
:NAME wp DESCRIPTION Manage WordPress through the command-line. SYNOPSIS wp SUBCOMMANDS cache Adds, removes, fetches, and flushes the WP Object Cache object. cap Adds, removes, and lists capabilities of a user role. cli Reviews current WP-CLI info, checks for updates, or views defined aliases. comment Creates, updates, deletes, and moderates comments. config Generates and reads the wp-config.php file. core Downloads, installs, updates, and manages a WordPress installation. cron Tests, runs, and deletes WP-Cron events; manages WP-Cron schedules. db Performs basic database operations using credentials stored in wp-config.php. embed Inspects oEmbed providers, clears embed cache, and more. eval Executes arbitrary PHP code. eval-file Loads and executes a PHP file. export Exports WordPress content to a WXR file. help Gets help on WP-CLI, or on a specific command. i18n Provides internationalization tools for WordPress projects. import Imports content from a given WXR file. language Installs, activates, and manages language packs. maintenance-mode Activates, deactivates or checks the status of the maintenance mode of a site. media Imports files as attachments, regenerates thumbnails, or lists registered image sizes. menu Lists, creates, assigns, and deletes the active theme's navigation menus. network Perform network-wide operations. option Retrieves and sets site options, including plugin and WordPress settings. package Lists, installs, and removes WP-CLI packages. plugin Manages plugins, including installs, activations, and updates. post Manages posts, content, and meta. post-type Retrieves details on the site's registered post types. rewrite Lists or flushes the site's rewrite rules, updates the permalink structure. role Manages user roles, including creating new roles and resetting to defaults. scaffold Generates code for post types, taxonomies, plugins, child themes, etc. search-replace Searches/replaces strings in the database. server Launches PHP's built-in web server for a specific WordPress installation. shell Opens an interactive PHP console for running and testing PHP code. sidebar Lists registered sidebars. site Creates, deletes, empties, moderates, and lists one or more sites on a multisite installation. super-admin Lists, adds, or removes super admin users on a multisite installation. taxonomy Retrieves information about registered taxonomies. term Manages taxonomy terms and term meta, with create, delete, and list commands. theme Manages themes, including installs, activations, and updates. transient Adds, gets, and deletes entries in the WordPress Transient Cache. user Manages users, along with their roles, capabilities, and meta. widget Manages widgets, including adding and moving them within sidebars.
Step 8: Install MySQL
-
Install MySQL: Install MySQL.
sudo apt install mysql-server -y
Once MySQL is installed, you can enhance its security using a built-in script that prompts you to adjust some insecure default settings. However, before running the script, you need to update the root user’s authentication method because, by default on Ubuntu installations, the root user does not have a password configured. If you skip this step, the script may fail and cause a recursive loop that can only be exited by closing the terminal window.
First, open the MySQL prompt:
sudo mysql
Next, execute the following command to update the root user’s authentication method to the more secure
caching_sha2_password
method and set a password:ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'password';
Finally, exit the MySQL prompt:
exit
-
Secure the MySQL Installation: Secure your MySQL server with the following command:
sudo mysql_secure_installation
Explanation of Each Step:
- Enter password for root: Input the root password set during MySQL installation.
- VALIDATE PASSWORD COMPONENT: This option tests and enforces strong passwords. Choose a validation level:
- 0 = LOW (minimum 8 characters),
- 1 = MEDIUM (includes numeric, mixed case, and special characters),
- 2 = STRONG (adds a dictionary file check).
- Change root password: Decide whether to change the root password.
- Remove anonymous users: Removes anonymous MySQL logins for better security.
- Disallow root login remotely: Prevents remote root access to MySQL, enhancing security.
- Remove test database: Deletes the default test database for a production environment.
- Reload privilege tables: Applies changes immediately.
Prompts will look like:
Securing the MySQL server deployment. Enter password for user
root: ********
VALIDATE PASSWORD COMPONENT can be used to test passwords and improve security. It checks the strength of password and allows the users to set only those passwords which are secure enough. Would you like to setup VALIDATE PASSWORD component?
Press y|Y for Yes, any other key for No: Y
There are three levels of password validation policy:
LOW Length >= 8 MEDIUM Length >= 8, numeric, mixed case, and special characters STRONG Length >= 8, numeric, mixed case, special characters and dictionary file
Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 2 Using existing password for root.
Estimated strength of the password: 50 Change the password for root ? ((Press y|Y for Yes, any other key for No) : Y
New password: ********
Re-enter new password: ********
Estimated strength of the password: 100 Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : Y By default, a MySQL installation has an anonymous user, allowing anyone to log into MySQL without having to have a user account created for them. This is intended only for testing, and to make the installation go a bit smoother. You should remove them before moving into a production environment.
Remove anonymous users? (Press y|Y for Yes, any other key for No) : Y Success.
Normally, root should only be allowed to connect from 'localhost'. This ensures that someone cannot guess at the root password from the network.
Disallow root login remotely? (Press y|Y for Yes, any other key for No) : Y Success.
By default, MySQL comes with a database named 'test' that anyone can access. This is also intended only for testing, and should be removed before moving into a production environment.
Remove test database and access to it? (Press y|Y for Yes, any other key for No) : Y
-
Dropping test database... Success.
-
Removing privileges on test database... Success.
Reloading the privilege tables will ensure that all changes made so far will take effect immediately.
Reload privilege tables now? (Press y|Y for Yes, any other key for No) : Y Success.
All done!
---
This concludes the complete setup of Nginx, PHP, and MySQL, along with securing the MySQL server. You now have a fully configured LAMP stack ready for use!