Apache – Force caching dynamic PHP content with mod_headers

Normally you want dynamic content to be fresh and not catchable. But sometimes it may be useful to cache it, like when you have website behind reverse proxy. To do this try something like this:

<filesmatch "\.(php|cgi|pl)$">
Header unset Pragma
Header unset Expires
Header set Cache-Control "max-age=3600, public"


Apache AuthBasic but excluding IP

Allow from IP without password prompt, and also allow from any address with password prompt

Order deny,allow
Deny from all
AuthName "htaccess password prompt"
AuthUserFile /web/askapache.com/.htpasswd
AuthType Basic
Require valid-user
Allow from
Satisfy Any


Apache – precompressing static files with gzip

Some time ago I’ve show how to precompress js and css file with gzip to be available for Nginx’s mod_gzip. In default configuration Apache don’t have such module but similar functionality could be achieved with few custom rewirtes.

Basically we will start with these rewrites to serve gzipped CSS/JS files if they exist and the client accepts gzip compression:

RewriteEngine on
RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.*)\.(js|css)$ $1\.$2\.gz [QSA]

Then we need to setup proper content types for such compressed files – I know how to do this in two ways:

  • pure rewrites with mod_header – witch should serve correct content type and prevent mod_deflate to gzip files that are already gzipped
    RewriteRule \.css\.gz$ - [T=text/css,E=no-gzip:1,E=manualgzip:1]
    RewriteRule \.js\.gz$ - [T=text/javascript,E=no-gzip:1,E=manualgzip:1]
    <ifmodule mod_headers.c>
    # setup this header only if rewrites above were used
    Header set Content-Encoding "gzip" env=manualgzip
  • by using Files clause (we could add this globally in httpd.conf)
    <files *.css.gz>
    ForceType text/css
    Header set Content-Encoding "gzip"
    <files *.js.gz>
    #ForceType text/javascript
    # lately this one is more popular
    ForceType application/javascript
    Header set Content-Encoding "gzip"

Both ways work fine. First one sets no-gzip variable to bypass second time compression. Second one rely on such option in my mod_deflate’s config:

SetEnvIfNoCase Request_URI \.(?:exe|t?gz|zip|bz2|sit|rar|gz)$ no-gzip dont-vary

which won’t compress any gz file, and this is why I have to setup Content-Encoding to gzip manually.

In both cases you will end with javacript and CSS files served from earlier prepared precomressed versions, with proper content type without engaging mod_deflate regardless you use js/css or js.gz/css.gz extension. But I strongly suggest to use extensions without gz – you will be able to disable this mechanism without any change in website code.

If you don’t know how to prepare files just look here.

I found another similar but BAD example – it’s using AddEncoding clause to add gzip content type to ALL gzip files – this will cause problems with other compressed files with gz extension ex. tar.gz. Don’t do this. My rules above are more selective.


Running Apache with mod_spdy and PHP-FPM

SPDY is new protocol proposed by Google as an alternative for HTTP(S). Currently Chrome and Firefox browsers are using it as default if available on server. It is faster in most cases by few to several percent. The side effect of using mod_spdy is that it’s working well only with thread safe Apache’s modules. PHP module for Apache is not thread safe so we need to use PHP as CGI or FastCGI service. CGI is slow – so running mod_spdy for performance gain with CGI is simply pointless. FastCGI is better but it’s not possible to share APC cache in FastCGI mode (ex. using spawn-fcgi), so it’s poor too. Best for PHP is PHP-FPM which is FastCGI service with dynamic process manager and could use full advantages of APC. In such configuration I could switch from apache prefork to worker which should use less resources and be more predictable.


On Squeeze we need to install dot.deb repository – instructions are here: http://www.dotdeb.org/instructions/

Then we could install:

apt-get install apache2-mpm-worker php5-fpm libapache2-mod-fastcgi

Now, mod_spdy – packages are available here: https://developers.google.com/speed/spdy/mod_spdy/ Choose your architecture.

wget https://dl-ssl.google.com/dl/linux/direct/mod-spdy-beta_current_i386.deb
dpkg -i mod-spdy-beta_current_i386.deb

Installation of this package will add automatically a new apt repository for mod_spdy.

If you have Apache’s module for PHP still installed you should remove it (you won’t need in anymore):

apt-get purge libapache2-mod-php5

Configuring PHP-FPM

First I’m changing php-fpm default pool configuration file – edit /etc/php5/fpm/pool.d/www.conf

; I want it to listen on socket, not on port
listen = /var/run/php5-fpm/site1.socket

;uncomment to set proper permission for socket
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

;uncomment and change to - PHP leaks, so kill child after 100 requests
pm.max_requests = 100

; for proper chroot handling we will need also
php_admin_value[doc_root] = /var/www/site1
php_admin_value[cgi.fix_pathinfo] = 0

Now restart php-fpm:

service php5-fpm restart

Connecting Apache with PHP-FPM

In VirtualHost paste this:

<IfModule mod_fastcgi.c>
  Alias /php5.fcgi /var/www/site1/php5.fcgi
  FastCGIExternalServer /var/www/site1/php5.fcgi -socket /var/lib/apache2/fastcgi/site1.socket
  AddType application/x-httpd-fastphp5 .php
  Action application/x-httpd-fastphp5 /php5.fcgi

  <Directory "/var/www/site1/">
    Order deny,allow
    Deny from all
    <Files "php5.fcgi">
      Order allow,deny
      Allow from all

Enable needed modules and restart Apache:

a2enmod actions
a2enmod fastcgi
service apache2 restart


SPDY requires encrypted connection so you need configured SSL (virtualhost running on port 443). Typical configuration for SSL looks similar to this:

# some random stuff - exactly like in you NON SSL configuration :-)
SSLEngine on

SSLCertificateFile    /etc/ssl/certs/example.com.crt
SSLCertificateKeyFile /etc/ssl/private/example.com.priv.key
SSLCACertificateFile  /etc/ssl/private/ca.crt


Should work now 🙂
So, use Chromium, enter the site you just configured and then on second tab go to: chrome://net-internals/#spdy. You should see your site there if it’s running on SPDY.
You could also use plugins for Firefox or Chromium to test if site is running on SPDY.

Advertise SPDY on HTTP

When you test if SPDY is working fine (and is faster in your configuration) you could advertise availability of SPDY protocol on your HTTP VirtualHost. Thanks to that when browser supports SPDY it will use it for faster access. To do this just add header in configuration:

Header set Alternate-Protocol "443:spdy/2"

There are more options that could be used, if you need just check docs here.

Certyfikaty nazwaSSL na własnym serwerze

Od jakiegoś czasu można kupić w NetArcie certyfikaty SSL, a niedawno zrobili na nie promocję – 15zł za pierwszy rok (za certyfikat na jedną stronkę). Tzw. tanie i dobre. Po wyrobieniu certyfikatu i zapisaniu z panelu klienta mam pliczki: stonka.crt i netart_rootca.crt, które wrzucamy do Apachego, powiedzmy tak:

SSLCertificateFile /etc/ssl/certs/stonka.crt
SSLCertificateKeyFile /etc/ssl/private/priv.key
SSLCACertificateFile /etc/ssl/certs/netart_rootca.crt

Certyfikat działa w Chromie ale nie weryfikuje się w Firefoxie i Internet Explorerze. FF wyświetla błąd: sec_error_unknown_issuer – co oznacza brak certyfikatu wystawcy gdzieś w łańcuchu certyfikatów. W FAQ zero jak chodzi o konfigurację certyfikatów na serwerze poza NetArt’em…

Przeglądnąłem informacje certyfikatu rootca:

openssl x509 -in netart_rootca.crt -text -noout
        Version: 3 (0x2)
        Serial Number:
        Signature Algorithm: sha1WithRSAEncryption
        Issuer: C=PL, O=Unizeto Technologies S.A., OU=Certum Certification Authority, CN=Certum Global Services CA
            Not Before: Jul  6 10:31:40 2012 GMT
            Not After : Jul  4 10:31:40 2022 GMT
        Subject: C=PL, O=NetArt Sp\xC3\xB3\xC5\x82ka Akcyjna S.K.A., OU=http://nazwa.pl, CN=nazwaSSL
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
            RSA Public Key: (2048 bit)
                Modulus (2048 bit):
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0
            X509v3 CRL Distribution Points: 

            Authority Information Access: 
                CA Issuers - URI:http://repository.certum.pl/gsca.cer

            X509v3 Authority Key Identifier: 

            X509v3 Subject Key Identifier: 
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Certificate Policies: 
                Policy: X509v3 Any Policy
                  CPS: https://www.certum.pl/CPS

    Signature Algorithm: sha1WithRSAEncryption

CA NetArtu nie jest domyślnie zainstalowane w żadnej przeglądarce więc nic dziwnego – ale są tam klucze Unizeto/Certum – dorzucę więc klucz CA (Chrome najwidoczniej sam potrafi to zrobić):

wget http://repository.certum.pl/gsca.cer
openssl x509 -inform der -in gsca.cer -out gsca.pem
cat gsca.pem >> netart_rootca.crt

Restart Apachego i przeglądarki już nie krzyczą. Mogliby się tylko wysilić na jakąś instrukcję albo udostępnienie od razu cabudle.crt z wszystkimi potrzebnymi certami.