Skip to content

Install LEMP on Ubuntu WSL on Windows 10

To facilitate web development, I wanted the same environment on my Windows 10 desktop as exists on my Ubuntu VPS server. With WSL (Windows Subsystem for Linux), I can have the same LEMP (Linux, Nginx, MySQL, and PHP) setup on my Windows 10 desktop.

See my previous post, Install Subversion on Ubuntu WSL on Windows 10, to get an Ubuntu-flavored WSL working on your Windows 10 system.

Instructions to install LEMP on Ubuntu WSL are almost the same as what I used to install LEMP on my Ubuntu server (see Install Ubuntu, LEMP, and WordPress on an Unmanaged VPS).

Install PHP

The latest Ubuntu WSL version is 18.04.1 LTS, which supports the latest PHP 7.2 version.

# Install PHP 7.2 and support packages:
# - php7.2-cli - PHP command line
# - php-mysql - PHP library to call MySQL database
# - php-fpm - PHP FastCGI Process Manager for Nginx integration
sudo apt install php7.2 php7.2-cli php7.2-mysql php7.2-fpm

# List the installed packages and grep for php-specific packages:
dpkg --get-selections | grep -i php

# Get the version of PHP installed
php --version

Install MySQL

# Install MySQL server.
sudo apt install mysql-server

# Get version of MySQL server and CLI installed
mysqld --version
mysql --version

# Start MySQL server
sudo service mysql start
sudo service mysql status

# Log into MySQL server as root user with blank password.
sudo mysql -u root
mysql> show databases;
mysql> quit

Note: Instead of using the “quit” command to exit the MySQL client, you can also use the “exit” command. Semi-colon termination (for example, “quit;” or “exit;”) are optional for these close connection commands.

By default, the MySQL server is configured to only allow root MySQL login from the root Linux user. Thus, we had to use “sudo mysql -u root” above. If we wish to allow a non-root Linux user to login using the root MySQL user (eliminate need for “sudo”), we’ll need to change how the root MySQL user is athenticated (switch to “mysql_native_password” plugin instead of the “auth_socket” plugin).

# Log into MySQL server as root user with blank password.
sudo mysql -u root

# Allow connection from a non-root Linux user
mysql> use mysql;
mysql> update user set plugin='mysql_native_password' where User='root';
mysql> flush privileges;

# Exit MySQL client.
mysql> quit

# Log into MySQL server as root user without "sudo".
mysql -u root
mysql> show databases;
mysql> quit

If you wish to secure the MySQL server, you can use the convenient “mysql_secure_installation” utility. Run the utility to enable password validation, set a root user password, remove anonymous users, disable remote root login, and remove the test database. You can re-run the utility again to reset the root user password if you need to.

# Run utility to secure MySQL server
sudo mysql_secure_installation

# Log into MySQL server as root user with password prompt
mysql -u root -p
mysql> show databases;
mysql> quit

If you wish to go back to using a blank MySQL root password, run the following to disable password validation and set a blank root password:

mysql -u root -p
mysql> uninstall plugin validate_password;
mysql> set PASSWORD for root@localhost=PASSWORD('');
mysql> quit

Install Nginx

# Install Nginx
sudo apt install nginx

# Start Nginx server
sudo service nginx start

Browse to http://localhost/ and you should see the initial Nginx welcome page.

Start the PHP-FPM service so Nginx can execute PHP scripts.

# Locate unix socket file which PHP-FPM service will listen to.
grep "listen =" /etc/php/7.2/fpm/pool.d/www.conf
# output: listen = /run/php/php7.2-fpm.sock

# Start PHP-FPM service.
sudo service php7.2-fpm start

Edit the default Nginx server block file to use the PHP-FPM service:

sudo nano /etc/nginx/sites-available/default

In the “default” server block file, change the following:

server {
        # Add index.php to the list if you are using PHP
        #index index.html index.htm index.nginx-debian.html;

        # Add index.php to front of "index" to execute it first
        index index.php index.html index.htm index.nginx-debian.html;

        # pass PHP scripts to FastCGI server
        #location ~ \.php$ {
        #       include snippets/fastcgi-php.conf;
        #       # With php-fpm (or other unix sockets):
        #       fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
        #       # With php-cgi (or other tcp sockets):
        #       fastcgi_pass;

        location ~ \.php$ {
                include snippets/fastcgi-php.conf;

                # Make sure unix socket path matches PHP-FPM configured path above
                fastcgi_pass unix:/run/php/php7.2-fpm.sock;

                # Prevent ERR_INCOMPLETE_CHUNKED_ENCODING when browser hangs on response
                fastcgi_buffering off;

Restart the Nginx service to have the changes take effect:

sudo service nginx restart

Test PHP and MySQL Integration

Note: On my Ubuntu WSL system, the Nginx default configuration block file (“/etc/nginx/sites-available/default”) sets the document root directory to be “root /var/www/html”. On your system, Nginx may use a different document root path so please double-check the Nginx block file if you encounter errors below.

Create a PHP test script by running this edit command:

sudo nano /var/www/html/info.php

In the “info.php” file, input the following content:


Browse to http://localhost/info.php and you should see a page containing information about the PHP installation.

Create a PHP MySQL test script by running this edit command:

sudo nano /var/www/html/mysql.php

In the “mysql.php” file, input the following content:

// HTML response header
header('Content-type: text/plain');

// Database connection parameters
$DB_HOST = 'localhost';
$DB_PORT = 3306; // 3306 is default MySQL port
$DB_USER = 'root';
$DB_PASS = ''; // blank or password (if you set one)
$DB_NAME = 'mysql'; // database instance name

// Open connection (all args can be optional or NULL!)
$mysqli = new mysqli($DB_HOST, $DB_USER, $DB_PASS, $DB_NAME, $DB_PORT);
if ($mysqli->connect_error) {
  echo 'Connect Error (' . $mysqli->connect_errno . ') ' . $mysqli->connect_error . PHP_EOL;
} else {
  // Query users
  if ($result = $mysqli->query('SELECT User FROM user')) {
    echo 'Database users are:' . PHP_EOL;
    for ($i = 0; $i < $result->num_rows; $i++) {
      $row = $result->fetch_assoc();
      echo $row['User'] . PHP_EOL;
  } else {
    echo 'Query failed' . PHP_EOL;

// Close connection

Browse to http://localhost/mysql.php and you should see a page listing the MySQL database users.

Debugging LEMP

To debug issues with LEMP, look at these log files:

MySQL: /var/log/mysql/error.log
Nginx: /var/log/nginx/error.log
PHP: /var/log/php7.2-fpm.log

For performance reasons, the debug logs from the PHP-FPM worker threads are discarded by default. If you wish to see error logs from your PHP applications, you will need to enable logging from worker threads.

Enable logging in the configuration of the PHP-FPM pool:

# Edit the PHP-FPM worker pool config file to enable logging
sudo nano /etc/php/7.2/fpm/pool.d/www.conf
   # Uncomment this line:
   catch_workers_output = yes

# Edit main PHP-FPM config file to set log level; otherwise you won't see any logs
sudo nano /etc/php/7.2/fpm/php-fpm.conf
   # Uncomment this line:
   log_level = notice

# Reload the PHP-FPM service to make the changes take effect
sudo service php7.2-fpm reload

You should now see error logs from the PHP worker threads outputted to the “/var/log/php7.2-fpm.log” file.

Tip: The PHP-FPM service may cache the PHP source files so after changing a PHP source file, you might need to reload the PHP-FPM service in order for the updated PHP code to execute.

Info above derived from:


    • Chanh

      Thanks for the stackoverflow link. Turns out that I had used Option 1 above, which is to switch the root user to use the “mysql_native_password” plugin instead of the default “auth_socket” plugin.

  1. Thank you!

    Everything is working. Long time google could not help me)))

  2. fazvil

    If done exactly according to the instructions, then when you try to execute
    sudo nano /var/www/html/info.php
    404 error, while the page localhost/ is working.
    It helped me if you specify in the settings server{…}

    • Chanh

      Great point. Thanks for pointing it out.

      I added a note to indicate that the Nginx default configuration block file may set the document root to be something different than “/var/www/html”.

Leave a Reply

Your email address will not be published. Required fields are marked *