---
title: "Virtualmin with Nginx and PHP 7 for Magento 2"
date: "2016-06-24"
path: "/179-virtualmin-with-nginx-and-php-7-for-magento-2.html"
section: "/ramblings"
description: "Building a Debian 8 Virtualmin server with Nginx, PHP 7.0 FPM and MariaDB to host Magento 2, including the change Virtualmin needs to use PHP-FPM."
---

Magento 2 runs noticeably better on PHP 7 than on 5.6, and Magento publish a ready-made Nginx config for it. I still wanted Virtualmin on the box to manage domains, users, DNS and mail. This is how I put the three together on Debian 8 (Jessie), mid-2016, for Magento 2.1.

The order matters: get PHP 7 in place before you create any virtual servers, because Virtualmin writes the PHP handler into each site's config at creation time.

## Virtualmin with Nginx

Install Virtualmin with the normal `install.sh`, then follow Virtualmin's own Nginx guide. In short:

```bash
service apache2 stop
update-rc.d apache2 disable
apt-get install nginx webmin-virtualmin-nginx webmin-virtualmin-nginx-ssl
```

In Virtualmin, under System Settings, Features and Plugins, turn on "Nginx website" and "Nginx SSL website" and turn off the Apache features. Remove Debian's stock site so it doesn't answer for every IP on the box:

```bash
rm /etc/nginx/sites-enabled/default
```

## PHP 7.0 from Dotdeb

Jessie only has PHP 5.6. Dotdeb packaged PHP 7.0 for it:

```bash
echo "deb http://packages.dotdeb.org jessie all" > /etc/apt/sources.list.d/dotdeb.list
wget https://www.dotdeb.org/dotdeb.gpg
apt-key add dotdeb.gpg
apt-get update

apt-get install php7.0-fpm php7.0-cli php7.0-mysql php7.0-gd php7.0-curl \
  php7.0-mcrypt php7.0-intl php7.0-xsl php7.0-mbstring php7.0-zip \
  php7.0-soap php7.0-json
```

That covers the extensions the Magento 2.1 installer checks for. FPM listens on `/run/php/php7.0-fpm.sock`.

Raise the PHP limits for the CLI and FPM. Magento's own guidance is at least 768M for `memory_limit`; set it in both `/etc/php/7.0/fpm/php.ini` and `/etc/php/7.0/cli/php.ini`, then `service php7.0-fpm restart`.

## Make Virtualmin use the FPM socket

Virtualmin's Nginx module expects to run a php-cgi process per domain and points `fastcgi_pass` at that. I wanted every site on the FPM socket instead. The template lives in the module's Perl, around line 254 of `virtual_feature.pl`:

```bash
nano /usr/share/webmin/virtualmin-nginx/virtual_feature.pl
```

Replace the block that builds the `fastcgi_pass` directive with:

```text
{ 'name' => 'fastcgi_pass',
  'words' => [ 'unix:/run/php/php7.0-fpm.sock' ],
},
```

Then in Virtualmin run System Settings, Re-Check Configuration so it notices PHP.

Two things to know about this. A Webmin update replaces the file, so check it after every upgrade. And one shared FPM pool means every site runs PHP as `www-data`. That suits a box dedicated to one store; on a shared server give each domain its own pool running as its own user.

## MariaDB

Magento 2.1 wants MySQL 5.6 or equivalent; Jessie's default is 5.5. I used MariaDB 10.1 from MariaDB's repository:

```bash
apt-get install software-properties-common
apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 0xcbcb082a1bb943db
echo "deb [arch=amd64,i386] http://ftp.osuosl.org/pub/mariadb/repo/10.1/debian jessie main" > /etc/apt/sources.list.d/mariadb.list
apt-get update
apt-get install mariadb-server
```

Any mirror from MariaDB's repository configuration tool will do for the `deb` line.

## The Nginx server block

Create the virtual server in Virtualmin as normal, then edit the file it writes under `/etc/nginx/sites-available/`. The upstream goes in its own file so that a second site doesn't try to define it again (Nginx refuses to start on a duplicate upstream):

```nginx
# /etc/nginx/conf.d/php7-fpm.conf
upstream fastcgi_backend {
    server unix:/run/php/php7.0-fpm.sock;
}
```

The server block, cut down to what matters. Virtualmin also writes a run of `fastcgi_param` lines into it; they can stay.

```nginx
server {
    server_name example.com www.example.com;
    listen 192.0.2.10:80;
    listen 192.0.2.10:443 ssl;

    ssl_certificate /home/example/ssl.cert;
    ssl_certificate_key /home/example/ssl.key;

    access_log /var/log/virtualmin/example.com_access_log;
    error_log /var/log/virtualmin/example.com_error_log;

    set $MAGE_ROOT /home/example/public_html;
    set $MAGE_MODE developer;
    include /etc/nginx/magento.conf;
}
```

## Magento's Nginx config

Magento 2 ships `nginx.conf.sample` in its root directory. Copy it rather than writing your own:

```bash
cp /home/example/public_html/nginx.conf.sample /etc/nginx/magento.conf
nginx -t && service nginx reload
```

It's worth reading through once. It sets the document root to `$MAGE_ROOT/pub`, so the application code isn't web-accessible at all. It denies the sensitive media folders (`customer`, `downloadable`, `import`) and `cron.php`. It only passes a short list of front controllers to PHP, so a stray `.php` file elsewhere in the tree won't execute. And it handles static file versioning, which is the part people get wrong when they write their own:

```nginx
location /static/ {
    if (!-f $request_filename) {
        rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last;
    }
}
```

Magento puts a version number in static URLs so browsers can cache them for a year. That rewrite strips it, and if the file hasn't been generated yet `static.php` builds it on demand.

`$MAGE_MODE` is passed through to PHP. Leave it on `developer` while building, then switch to `production` once the code is deployed (`bin/magento deploy:mode:set production`) and change the variable to match, so the long expiry headers in the sample config apply.

## Sample data

On a test build it's useful to have a populated catalogue. From the Magento root, as the domain's user:

```bash
bin/magento sampledata:deploy
bin/magento setup:upgrade
```

Then reboot the server once to make sure Nginx, PHP-FPM and MariaDB all come back on their own.

Today: Dotdeb is no longer maintained, PHP 7.0 and Debian 8 are long out of support, and current Magento 2.4 needs PHP 8 and OpenSearch. The shape of the setup still applies; take PHP from Ondřej Surý's repository or the distribution instead.
