Manual for Setting Up a LAMP Stack with Nginx, PHP, and MySQL

Prerequisites

  1. You must have a fresh Ubuntu 24.04 server with root privileges.
  2. 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ý.

  1. Add the Repository: Add the PPA (Personal Package Archive) containing the latest Nginx packages.

    sudo add-apt-repository ppa:ondrej/nginx -y
  2. Update the Package Lists: Refresh the list of available packages.

    sudo apt update
  3. Upgrade Existing Packages: Update and upgrade outdated packages on your server.

    sudo apt dist-upgrade -y
  4. Install Nginx: Install Nginx.

    sudo apt install nginx -y
  5. 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

  1. 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
  2. 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 to on. This makes each worker process accept all new connections simultaneously rather than one at a time.
    • 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 the sendfile on; directive.
    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;
    }
  3. Restart Nginx: Test the configuration and restart Nginx.

    sudo nginx -t
    sudo service nginx restart

Step 4: Install PHP 8.3

  1. Add PHP Repository: Add the PHP repository for the latest version of PHP.

    sudo add-apt-repository ppa:ondrej/php -y
    sudo apt update
  2. 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).
  1. Verify PHP Installation: Check the installed PHP version.

    php-fpm8.3 -v

Step 5: Configure PHP and PHP-FPM

  1. 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
  2. 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 the file_override line we need to modify. Uncomment it (remove the semicolon) and change the value from zero to one:

    opcache.enable_file_override = 1
  3. 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 few nginx 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:

  1. 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.

  2. Test and Restart Nginx: Test the Nginx configuration and restart the service.

    sudo nginx -t
    sudo service nginx restart
  3. 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 the info.php file.

    sudo chown user /var/www/html/info.php
  4. 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
  5. 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.

  1. 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.

  2. 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
  3. Verify Installation: Test WP-CLI:

    wp --info
  4. 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

  1. 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
  2. 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!