François' Blog

Apache and PHP-FPM

Published on 2015-11-01 | Last modified on 2015-11-02

There is lots of crappy information out there about deploying PHP with Apache, or nginx. It is really hard to distill what is really a safe configuration and what works. Combining this with a safe TLS configuration nears the impossible.

PHP-FPM

Configuring PHP-FPM is not that difficult, actually, one could keep the defaults and that will work pretty well.

$ sudo dnf -y install php-fpm

I only change the configuration not to use a socket, but listen on TCP instead. There are some more tweaks you can perform, but to get it working reasonably well that is not needed yet.

$ sudo sed -i "s|listen = /run/php-fpm/www.sock|listen = [::]:9000|" /etc/php-fpm.d/www.conf
$ sudo sed -i "s/listen.allowed_clients = 127.0.0.1/listen.allowed_clients = 127.0.0.1,::1/" /etc/php-fpm.d/www.conf

You possibly have to update the listen.allowed_clients if you use a separate VM or container for the web server.

Do not forget to enable and start PHP-FPM.

$ sudo systemctl enable php-fpm
$ sudo systemctl start php-fpm

That should be all for PHP-FPM.

Apache

We start simple, with a HTTP server serving a PHP application using PHP-FPM.

<VirtualHost www.example.org:80>
    ServerName www.example.org

    ErrorLog logs/www.example.org_error_log
    TransferLog logs/www.example.org_access_log
    CustomLog logs/www.example.org_combined_log combined
    LogLevel warn

    DocumentRoot /usr/share/my-php-app/web

    <Directory "/usr/share/my-php-app/web">
        Options -MultiViews

        #Require local
        Require all granted

        AllowOverride none
    </Directory>

    # Pass through the "Authorization" header
    SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1

    # Some request are handled by Apache directly
    ProxyPass      "/css/" !
    ProxyPass      "/img/" !
    ProxyPass      "/js/" !
    ProxyPassMatch "^/robots.txt$" !
    ProxyPassMatch "^/favicon.ico$" !

    # The rest goes to PHP-FPM...
    ProxyPass      "/" fcgi://[::1]:9000/usr/share/php-my-app/web/index.php/
</VirtualHost>

Apache TLS

Basically, this means that we remove the current contents of the VirtualHost block and use it to rewrite to HTTPS instead and move the PHP-FPM stuff to the new TLS VirtualHost.

<VirtualHost www.example.org:80>
    ServerName www.example.org

    ErrorLog logs/www.example.org_error_log
    TransferLog logs/www.example.org_access_log
    CustomLog logs/www.example.org_combined_log combined
    LogLevel warn

    RewriteEngine On
    RewriteCond %{HTTPS} !=on
    RewriteCond %{ENV:HTTPS} !=on
    RewriteRule .* https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L]
</VirtualHost>

Now, we create a new TLS VirtualHost that contains the stuff from the previous section and some extra TLS configuration options.

<VirtualHost www.example.org:443>
    ServerName www.example.org

    ErrorLog logs/www.example.org_ssl_error_log
    TransferLog logs/www.example.org_ssl_access_log
    CustomLog logs/www.example.org_ssl_combined_log combined
    LogLevel warn

    DocumentRoot /usr/share/php-my-app/web

    SSLEngine on
    SSLCertificateFile /etc/pki/tls/certs/www.example.org.crt
    #SSLCertificateChainFile /etc/pki/tls/certs/www.example.org-chain.crt
    SSLCertificateKeyFile /etc/pki/tls/private/www.example.org.key

    SSLProtocol             all -SSLv3 -TLSv1
    SSLCipherSuite          ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK
    SSLHonorCipherOrder     on
    SSLCompression          off

    # OCSP Stapling, only in httpd 2.3.3 and later
    SSLUseStapling          on
    SSLStaplingResponderTimeout 5
    SSLStaplingReturnResponderErrors off

    # HSTS (mod_headers is required) (15768000 seconds = 6 months)
    Header always set Strict-Transport-Security "max-age=15768000"

    <Directory "/usr/share/php-my-app/web">
        Options -MultiViews

        Require all granted
        AllowOverride none
    </Directory>

    # Pass through the "Authorization" header
    SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1

    # Some request are handled by Apache directly
    ProxyPass      "/css/" !
    ProxyPass      "/img/" !
    ProxyPass      "/js/" !
    ProxyPassMatch "^/robots.txt$" !
    ProxyPassMatch "^/favicon.ico$" !

    # The rest goes to PHP-FPM...
    ProxyPass      "/" fcgi://[::1]:9000/usr/share/php-my-app/web/index.php/
</VirtualHost>

Now we still need to generate the key and certificate and optionally have them signed by some CA. The following commands make this very easy:

# Generate the private key
$ sudo openssl genrsa -out /etc/pki/tls/private/www.example.org.key 2048
$ sudo chmod 600 /etc/pki/tls/private/www.example.org.key

# Create the CSR (optionally, send this to CA to have signed)
$ sudo openssl req -subj "/CN=www.example.org" -sha256 -new -key /etc/pki/tls/private/www.example.org.key -out www.example.org.csr

# Create the (self signed) certificate and install it
$ sudo openssl req -subj "/CN=www.example.org" -sha256 -new -x509 -key /etc/pki/tls/private/www.example.org.key -out /etc/pki/tls/certs/www.example.org.crt

If you want to have the certificate signed by a CA, use the CSR generated above and send it to the CA. Once you get a certificate back, overwrite the self signed certificate in /etc/pki/tls/certs/www.example.org.crt and make sure to also configure the SSLCertificateChainFile.

Next you can just place the two VirtualHost sections above in one file, put it in /etc/httpd/conf.d/www.example.org.conf and enable and start Apache.

$ sudo systemctl enable httpd
$ sudo systemctl start httpd

Unanswered Questions

This stuff is so complex that there are still some issues that I do not know how to solve. Hopefully this list will become smaller over time.

Resources

These resources are a MUST. Make sure to take note of everything that is mentioned there! Do not trust what I write here without thinking for yourself and making sure you understand everything.

History