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.
- The URL passed to PHP-FPM is still "URL encoded", for example
%20
is not converted back toSPACE
; - Should we actually have the
RewriteCond
rules in the HTTPVirtualHost
section? It MUST always be rewritten?
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.
- PHP: The Right Way (Servers and Deployment)
- Mozilla SSL Configuration Generator
- SSL Server Test (Qualys)
- SSL Decoder (Remy van Elst)
- Earlier blog post on HTTPS