Configurare un proxy Apache per Docker con SSL
Alla ricerca della documentazione perduta
Oggigiorno se si deve configurare un web proxy per esporre un servizio gestito tramite Docker, la maggior parte della documentazione e delle guide online fa riferimento ad Nginx, il poco materiale che si trova per Apache invece è obsoleto o semplicemente non funziona. Ma perché scrivere una guida per un proxy Apache per Docker? Semplice: ci sono ancora situazioni in cui non è possibile sostituire Nginx ad Apache e quindi c’è la necessità un proxy Apache che punta ad un docker con a sua volta il web server Apache.
La difficoltà sta nel configurare tutto correttamente, soprattutto per quanto riguarda il certificato SSL per la connessione HTTPS.
Questa guida quindi sarà divisa in due parti, una prima parte con la configurazione del proxy, installato direttamente nel server, ed una seconda parte con la configurazione dei servizi Docker.
Gli esempi di questo articolo sono stati scritti in un server Debian Linux, ma sono facilmente adattabili a qualunque altra distribuzione.
Parte 1: come configurare il proxy Apache
Generazione certificato SSL
Per la generazione del certificato SSL ci affidiamo a Certbot / Let’s Encrypt. In questo articolo non spiegherò come installare Certbot, la guida ufficiale è più che sufficiente.
Per creare il certificato basta avere il DNS configurato ed eseguire questo comando che non ha tanto bisogno di spiegazioni:
certbot certonly -d example.com --apache -m "info@example.com" --agree-tos --no-eff-email
Moduli Apache
Per il corretto funzionamento di Apache come proxy server per gestire connessioni HTTPS è necessaria l’attivazione dei seguenti moduli:
- http2
- proxy
- proxy_http
- ssl
- socache_shmcb
Si possono attivare con il seguente comando:
a2enmod http2 proxy proxy_http ssl socache_shmcb
Configurazione Virtual Host
Per comodità la configurazione del virtual host preferisco effettuarla in due file: uno per la versione http ed uno per la versione https, con il primo che farà un redirect al secondo.
Configurazione Virtual Host sulla porta 80 (http)
Il virtual host per la porta 80 semplicemente reindirizza alla versione HTTPS.
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
RewriteEngine on
RewriteCond %{SERVER_NAME} =example.com [OR]
RewriteCond %{SERVER_NAME} =www.example.com
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
ErrorLog ${APACHE_LOG_DIR}/example.com.error.log
CustomLog ${APACHE_LOG_DIR}/example.com.access.log combined
</VirtualHost>
Configurazione Virtual Host sulla porta 443 (https)
Il virtual host per la porta 443 invece ha due sezioni da commentare.
La prima riguarda il proxy vero e proprio: attenzione a mettere il nome di dominio corretto nelle direttive ProxyPass
e ProxyPassReverse
. In queste due direttive va messa la porta HTTPS in cui Docker sarà in ascolto.
La seconda sezione è quella relativa ad SSL dove in sostanza viene semplicemente caricata la configurazione di Let’s Encrypt ed indicati i percorsi dei file di certificato.
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
ErrorLog ${APACHE_LOG_DIR}/example.com.error.log
CustomLog ${APACHE_LOG_DIR}/example.com.access.log combined
ProxyRequests on
ProxyPreserveHost On
ProxyPass / https://www.example.com:8080/
ProxyPassReverse / https://www.example.com:8080/
SSLProxyEngine On
SSLProxyVerify none
SSLProxyCheckPeerCN off
SSLProxyCheckPeerName off
SSLProxyCheckPeerExpire off
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/www.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/www.example.com/privkey.pem
</VirtualHost>
</IfModule>
Parte 2: come configurare Docker
Servizio Docker
Per la gestione di Docker normalmente mi trovo bene con Docker Compose, ecco quindi la configurazione del servizio Apache in tandem con PHP-FPM.
Per il container PHP-FPM utilizzo devilbox/php-fpm che funziona molto bene, mentre per Apache l’immagine “httpd:alpine” è valida nella maggior parte dei casi.
Ecco un esempio di file docker-compose.yml
.
version: '3'
services:
#
# PHP-FPM
#
www_example_com:
image: devilbox/php-fpm:7.4-mods
container_name: www_example_php
restart: unless-stopped
networks:
- www_example_com
volumes:
- ./data/www:/srv/www/www.example.com/
- ./conf/php-fpm/www.conf:/usr/local/etc/php-fpm.d/www.conf
- ./conf/php/conf.d/docker-php-ext-opcache.ini:/usr/local/etc/php/conf.d/docker-php-ext-opcache.ini
- ./logs/php-fpm:/var/log/php-fpm
user: "${UID}:${GID}"
environment:
TIMEZONE: "Europe/Rome"
ENABLE_MAIL: 1
DEBUG_ENTRYPOINT: 0
#
# Apache
#
www_example_apache:
image: httpd:alpine
container_name: www_example_apache
restart: unless-stopped
networks:
- www_example_net
ports:
- 8081:443
volumes:
- ./data/www:/srv/www/www.example.com/
- ./conf/apache2/httpd.conf:/usr/local/apache2/conf/httpd.conf
- ./conf/apache2/www.example.com.conf:/usr/local/apache2/conf/extra/httpd-vhosts.conf
- /etc/letsencrypt/live/www.example.com/fullchain.pem:/usr/local/apache2/conf/fullchain.pem
- /etc/letsencrypt/live/www.example.com/privkey.pem:/usr/local/apache2/conf/privkey.pem
- ./logs/apache2:/usr/local/apache2/logs
depends_on:
- www_example_php
networks:
www_example_net:
Entrambi i servizi montano la directory ./data/www
che contiene il sito e dei file di configurazione. Eccoli qui di seguito, uno per uno:
File di configurazione dei servizi Docker
Configurazione pool FPM (www.conf
)
Qui va segnalata la porta 9000 che poi andrà usata nella configurazione di Apache. Ho anche attivato il log degli errori di FPM sempre molto utile.
[www]
user = www-data
group = www-data
listen = 9000
pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
catch_workers_output = yes
php_admin_flag[log_errors] = on
php_admin_flag[display_errors] = off
php_admin_value[error_reporting] = E_ALL & ~E_NOTICE & ~E_WARNING & ~E_STRICT & ~E_DEPRECATED
php_admin_value[error_log] = /var/log/php-fpm/php-fpm.error.log
Configurazione PHP Opcache (docker-php-ext-opcache.ini
)
Per ottimizzare le prestazioni di PHP utilizzo opcache. Attenzione che si tratta di impostazioni un po’ aggressive e se si modificano i file PHP potrebbe essere necessario il riavvio del servizio.
[opcache]
# Carica la libreria Opcache
zend_extension=opcache
# Attivo opcache
opcache.enable=1
; https://www.scalingphpbook.com/blog/2014/02/14/best-zend-opcache-settings.html
; 0 means it will check on every request
; 0 is irrelevant if opcache.validate_timestamps=0 which is desirable in production
opcache.revalidate_freq=0
; in produzione si mette 0, in -dev si mette 1, quindi si inibisce il parametro precedente
; attenzione però, quando si fanno modifiche ai file PHP bisogna riavviare fpm
opcache.validate_timestamps=0
; dipende dal numero di file PHP
; Quindi con il comando seguente vedo circa quanti file PHP ci sono:
; find . -type f -print | grep php | wc -l
opcache.max_accelerated_files=20000
; si può ottimizzare con opcache_get_status() per vedere quanta memoria si sta usando
opcache.memory_consumption=192
opcache.max_wasted_percentage=10
opcache.interned_strings_buffer=16
opcache.fast_shutdown=1
Configurazione web server Apache (httpd.conf
)
Qui ho preso la configurazione di default dell’immagine docker httpd ed ho semplicemente decommentato le righe riguardanti dei moduli da attivare. Ecco quali sono:
LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
LoadModule request_module modules/mod_request.so
LoadModule include_module modules/mod_include.so
LoadModule filter_module modules/mod_filter.so
LoadModule deflate_module modules/mod_deflate.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule env_module modules/mod_env.so
LoadModule expires_module modules/mod_expires.so
LoadModule headers_module modules/mod_headers.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule version_module modules/mod_version.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
LoadModule ssl_module modules/mod_ssl.so
LoadModule http2_module modules/mod_http2.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule status_module modules/mod_status.so
LoadModule autoindex_module modules/mod_autoindex.so
Il resto del file è invariato.
Configurazione Virtual Host dentro Docker (www.example.com.conf
)
Nella documentazione ufficiale dell’immagine docker httpd si suggerisce di usare il file di configurazione specifico per SSL, però non si addice benissimo alla situazione con virtual host, quindi preferisco avere la configurazione di SSL direttamente nel file di configurazione del virtual host.
Attenzione: ricordarsi di mettere la porta corretta per FPM ed i percorsi dei certificati di Let’s Encrypt che sono stati montati tramite Docker Compose.
Listen 443
SSLCipherSuite HIGH:MEDIUM:!MD5:!RC4:!3DES
SSLProxyCipherSuite HIGH:MEDIUM:!MD5:!RC4:!3DES
SSLHonorCipherOrder on
SSLProtocol all -SSLv3
SSLProxyProtocol all -SSLv3
SSLPassPhraseDialog builtin
SSLSessionCache "shmcb:/usr/local/apache2/logs/ssl_scache(512000)"
SSLSessionCacheTimeout 300
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
DocumentRoot "/srv/www/www.example.com"
ErrorLog "logs/www_example_error.log"
CustomLog "logs/ww_example_access.log" combined
ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://www_example_php:9000/srv/www/www.example.com/$1
SSLEngine on
SSLCertificateFile "/usr/local/apache2/conf/fullchain.pem"
SSLCertificateKeyFile "/usr/local/apache2/conf/privkey.pem"
<Directory "/srv/www/www.example.com">
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
DirectoryIndex index.php
</Directory>
</VirtualHost>
Conclusione
Abbiamo quindi visto tutta la configurazione per far funzionare un proxy Apache con supporto ad SSL. Chiaramente si tratta id una soluzione semplice, adatta per un singolo sito. Sicuramente si può implementare qualcosa di più evoluto. Però questa è la configurazione di base, la più semplice che ho trovato e la più facilmente replicabile e si può usare come punto di partenza per progetti più complessi.
Per concludere ricordo che se si deve gestire un server in produzione conviene però convertire il tutto in un sistema di gestione tramite strumenti tipo Ansible in modo da rendere più robusto e flessibile il sistema. Magari quando avrò un po’ di tempo potrei scrivere qualche altro appunto.