aboutsummaryrefslogtreecommitdiffstats
path: root/posts/apache_php_fpm.md
blob: 38fb4eaf2050bbcb0ba7bd06dd3d6c4e7a6fa1aa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
---
title: Apache and PHP-FPM
published: 2015-11-01
modified: 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 to `SPACE`;
- Should we actually have the `RewriteCond` rules in the HTTP `VirtualHost` 
  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](http://www.phptherightway.com/#servers_and_deployment) (Servers and Deployment)
- [Mozilla SSL Configuration Generator](https://mozilla.github.io/server-side-tls/ssl-config-generator/)
- [SSL Server Test](https://www.ssllabs.com/ssltest/) (Qualys)
- [SSL Decoder](https://ssldecoder.org/) (Remy van Elst)  
- [Earlier blog post on HTTPS](https.html)