DB-유

Installing the LEMP Stack on Ubuntu 16.04

· linux, nginx, mysql, php

Most of the WordPress, Nextcloud and custom PHP sites I look after run on LEMP: Linux, nginx (the E is for "engine-x"), MySQL and PHP. I've rebuilt this stack from memory so many times that I finally wrote it down. Ubuntu 16.04's own repository is recent enough (nginx 1.10, MySQL 5.7, PHP 7.0), so no third-party sources are needed.

Below is a clean 16.04 server with PHP-FPM talking to nginx over a Unix socket. That's what I use on a single server. You need a sudo user and ufw allowing SSH; a hostname helps but isn't required.

nginx first

sudo apt-get update
sudo apt-get install -y nginx
sudo ufw allow 'Nginx Full'
systemctl status nginx

Browse to the server's IP address; you should see the "Welcome to nginx!" page.

MySQL 5.7

sudo apt-get install -y mysql-server

The installer asks for a root password. Set one, then run the hardening script, which removes the anonymous user and test database and disables remote root login:

sudo mysql_secure_installation

I answer yes to everything except the VALIDATE PASSWORD PLUGIN question, since I'd rather pick my own passwords than fight a policy. Confirm you can log in:

mysql -u root -p -e 'SELECT VERSION();'

On 16.04 you should see 5.7.x-0ubuntu0.16.04.x.

PHP 7.0 with FPM

Don't install the php metapackage; it pulls in Apache, which I found out after wondering why port 80 was busy. Install FPM and the extensions most applications need explicitly:

sudo apt-get install -y php7.0-fpm php7.0-mysql php7.0-curl php7.0-gd \
  php7.0-mbstring php7.0-xml php7.0-zip php7.0-json php7.0-opcache

One security setting in /etc/php/7.0/fpm/php.ini is worth changing right away. Find cgi.fix_pathinfo, uncomment it and set it to 0 so that PHP will not try to execute the closest matching file when a request points at a non-existent script:

cgi.fix_pathinfo=0

Then restart FPM:

sudo systemctl restart php7.0-fpm

FPM listens on /run/php/php7.0-fpm.sock by default, which is what the nginx config below refers to.

Wiring nginx to FPM

Create a site file and disable the default:

sudo nano /etc/nginx/sites-available/example.com
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    root /var/www/example.com/public_html;
    index index.php index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php7.0-fpm.sock;
    }

    location ~ /\.ht {
        deny all;
    }
}

The snippets/fastcgi-php.conf file shipped with Ubuntu's nginx sets SCRIPT_FILENAME and the other FastCGI parameters, so you do not need to repeat them. Enable the site:

sudo mkdir -p /var/www/example.com/public_html
sudo chown -R $USER:$USER /var/www/example.com
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

A database for the app

Applications shouldn't connect as root. Make a dedicated database and user:

CREATE DATABASE appdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'a-long-random-password';
GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;

Does PHP actually work?

Drop a test script into the document root:

echo '<?php phpinfo();' > /var/www/example.com/public_html/info.php

Load http://example.com/info.php. The page should report Server API: FPM/FastCGI and list mysqlnd and pdo_mysql under loaded modules. Then check the database connection from PHP:

php -r '$m = new mysqli("localhost","appuser","a-long-random-password","appdb"); echo $m->server_info, PHP_EOL;'

Delete info.php when you are done; it discloses far too much about the server to leave lying around.

When it goes wrong

  • Browser downloads the .php file instead of running it. The location ~ \.php$ block is missing or the site was not reloaded. nginx -T | grep fastcgi_pass shows whether the directive is active.
  • 502 Bad Gateway with connect() to unix:/run/php/php7.0-fpm.sock failed (2: No such file or directory) in /var/log/nginx/error.log. PHP-FPM is not running, or a different socket path is set in /etc/php/7.0/fpm/pool.d/www.conf under listen. Start FPM or align the paths.
  • ERROR 1698 (28000): Access denied for user 'root'@'localhost'. On Ubuntu's MySQL 5.7, root uses the auth_socket plugin when no password was set during install, so you must use sudo mysql rather than mysql -u root -p. This is by design, not a bug.
  • File not found. from PHP on every request. The root directive sits inside a location block instead of the server block, so FPM receives the wrong SCRIPT_FILENAME.

Next I'd add a Let's Encrypt certificate, then look at pm.max_children in the FPM pool so it matches the RAM you actually have.