Nginx als reverse proxy voor Apache configureren

15 augustus, 2014
Handleidingen
Proxy, Nginx, Apache

Nginx is een webserver die dankzij een event-driven-architectuur sneller en meer verzoeken (requests) per seconde kan verwerken dan menig andere webserver, waaronder de Apache HTTP Server (httpd). De efficiëntere werking zorgt ervoor dat zowel de processor als het geheugen van het systeem minder zwaart belast worden. Omdat Apache nog altijd veelgebruikte software is, en enige omscholing, tijd en soms zelfs het herprogrammeren van websites nodig is om volledig van webserver te wisselen, wordt er regelmatig voor gekozen om nginx als zogeheten reverse proxy te gebruiken.

Het installeren van een reverse proxy houdt in dat alle binnenkomende verzoeken allereerst door de proxysoftware, in dit geval nginx, gefilterd worden. In het geval dat een verzoek volgens vooropgestelde regels door Apache verder behandeld dient te worden, bijvoorbeeld omdat het een dynamische webpagina betreft, stuurt de proxy het verzoek door naar Apache, dat op de achtergrond actief is maar niet direct door de buitenwereld benaderbaar is.

Nginx wordt veelal ingezet om alle verzoeken voor statische bestanden, zoals afbeeldingen, stylesheets, JavaScript en Word- of PDF-documenten, direct te behandelen, zonder Apache daarmee te belasten. Wanneer een verzoek een specifieke functie van Apache vereist, bijvoorbeeld de interpretatie van een PHP-script met behulp van de mod_php-extensie, dan ontvangt Apache dit verzoek van nginx, en stuurt het resultaat na verwerking weer terug.

De reverse proxy is ontzichtbaar voor Apache. Dat wil zeggen: Apache ziet geen verschil tussen verzoeken die direct of via de proxy worden ingediend. Het grote voordeel hiervan is dat de configuratie van Apache nauwelijks hoeft worden aangepast om te kunnen profiteren van de voordelen die nginx biedt.

In deze handleiding gaan we ervan uit dat de Apache HTTP Server reeds geïnstalleerd en geconfigureerd is om verzoeken voor een bepaalde domeinnaam (example.com) af te handelen. Verder is enige vertrouwdheid met de Linux commando-regel en het gebruik van een teksteditor zoals Nano gewenst.

Nginx installeren

Is nginx nog niet geïnstalleerd op het systeem, gebruik dan de package manager van de betreffende Linux-distributie om het te installeren:

Ubuntu/Debian:

apt-get install nginx

Beide distributies bevatten een versie van nginx in de standaard repositories (softwaremagazijnen). Als alternatief is het ook mogelijk om de officiële packages vanaf nginx.org te installeren. Voeg in dat geval de volgende regels aan /etc/apt/sources.list toe, en voer dan pas bovenstaand commando uit:

Ubuntu:

deb http://nginx.org/packages/ubuntu/ lucid nginx
deb-src http://nginx.org/packages/ubuntu/ lucid nginx

Debian:

deb http://nginx.org/packages/debian/ squeeze nginx
deb-src http://nginx.org/packages/debian/ squeeze nginx

CentOS/Fedora:

yum install nginx

Bevatten de standaard CentOS-repositories niet de nginx-software, maak dan het bestand /etc/yum.repos.d/nginx.repo aan met de inhoud:

[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=0
enabled=1

Na installatie starten we nginx nog niet direct, omdat Apache reeds actief is op poort 80 (voor HTTP-verkeer).

Nginx configureren als reverse proxy

Omdat nginx voortaan de verbindingen met de buitenwereld zal verzorgen, moeten enkele instellingen vanuit Apache naar nginx worden overgezet, bijvoorbeeld voor KeepAlive en het met gzip comprimeren van niet-binaire bestanden (HTML, JavaScript, CSS, TXT, enzovoorts). De precieze configuratie van nginx is dan ook afhankelijk van de huidige configuratie van Apache, maar onderstaand voorbeeld zou in de meeste gevallen moeten volstaan.

We bewerken allereerst /etc/nginx/nginx.conf. Dikgedrukte regels zijn aangepast of toegevoegd aan de standaardconfiguratie:

user              nginx;
worker_processes  2; # Stel worker_processes gelijk aan het aantal processorkernen

error_log   /var/log/nginx/error.log warn;
pid         /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                '$status $body_bytes_sent "$http_referer" '
                '"$http_user_agent"
                "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    sendfile on;
    #tcp_nopush on;

    keepalive_timeout 60; # Aantal seconden dat verbindingen open gehouden worden (optioneel)

    gzip on; # Compressie van niet-binaire bestanden inschakelen (optioneel)

    include /etc/nginx/conf.d/*.conf;
}

De regel include /etc/nginx/conf.d/*.conf zorgt ervoor dat alle .conf-bestanden in de map /etc/nginx/conf.d bij het (her)starten van nginx worden uitgelezen. Op die manier is het mogelijk om in overzichtelijke configuratiebestanden unieke regels op te stellen voor elke website op deze webserver.

Let op: de standaardconfiguratie van nginx kan verschillen per Linux-distributie, en is afhankelijk van de bron waaruit de software geïnstalleerd is. De voorbeelden die in deze handleiding gebruikt worden, zijn afkomstig uit de officiële software packages (zie "Nginx installeren").

Voor iedere website maken we nu een configuratiebestand eindigend op .conf aan in de map /etc/nginx/conf.d, bijvoorbeeld example.com.conf:

server {

    listen 80;

    server_name example.com www.example.com;

    access_log /home/example.com/logs/nginx_access.log;
    error_log /home/example.com/logs/nginx_error.log error;

    root /home/example.com/public_html;

    index index.php index.html index.htm;
    charset utf-8;

    location / {

        try_files $uri $uri/ /index.php;

    }

    location ~ \.php$ {

        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header Host $host;
        proxy_pass http://127.0.0.1:8080;

    }

    location ~ /\. {

        deny all;
        access_log off;
        log_not_found off;

    }

}

De instructie server_name bevat de domeinnamen die aan deze website gekoppeld zijn. De locatie van de bestanden voor die website, hierboven /home/example.com, bij de instructies access_log, error_log en root, is doorgaans gelijk aan de locatie die in Apache aan de website gekoppeld is.

De root instructie vertelt nginx in welke map het naar een bestand overeenkomstig met het verzoek moet zoeken. Het adres example.com/afbeelding.jpg staat hier gelijk aan de locatie /home/example.com/public_html/afbeelding.jpg. Met try_files wordt nginx opgedragen eerst te zoeken naar de fysieke aanwezigheid van een bestand binnen de root, zoals het eerder genoemde afbeelding.jpg. Wordt dit bestand niet gevonden, dan probeert nginx het nogmaals met een toegevoegde schuine streep ($uri/) voor het geval het niet een bestand maar een map betreft. Wordt het bestand of de map alsnog niet op de schijf gevonden, dan stuurt nginx het verzoek door naar /index.php, en verzoeken voor .php-bestanden worden opgepikt door location ~ \.php$.

Het location ~ \.php$ locatieblok is verantwoordelijk voor de communicatie met Apache. Elk verzoek om een .php-bestand, inclusief alle bestanden die aan de hand van try_files niet door nginx gevonden kunnen worden, wordt via de proxy_pass-instructie doorgestuurd naar poort 8080 op hetzelfde systeem (127.0.0.1 is het adres van de lokale loopback-interface). Eerst worden echter drie HTTP-headers toegevoegd, zodat Apache weet om welke website het gaat (X-Host) en het IP-adres van de bezoeker (client) kan zien (X-Real-IP, X-Forwarded-For) zonder er rechtstreeks verbinding mee te hebben.

Het laatste blok is een beveiligingsmaatregel die voorkomt dat alle bestanden waarvan de naam met een punt begint, zoals het door Apache gebruikte .htaccess en .htpasswd (dat inloggegevens bevat), via de nginx-proxy door de buitenwereld op te vragen zijn.

Apache naar poort 8080

De enige wijziging die we in de Apache-configuratie hoeven door te voeren is het poortnummer waar de webserver zich aan verbindt. Omdat nginx alle binnenkomende HTTP-verzoeken op poort 80 zal behandelen, moet Apache op de achtergrond wijken naar een alternatieve poort. Om Apache in te stellen op poort 8080 uit het bovenstaande voorbeeld, openen we /etc/httpd/conf/httpd.conf en zoeken we naar de Listen-instructie.

Poort 80 wijzigen we hier naar 8080. Deze instructie kan in Apache ook per VirtualHost worden bepaald.

# Listen: Allows you to bind Apache to specific IP addresses and/or
# ports, in addition to the default. (...)
Listen 8080

Reverse proxy activeren

Nu Apache en nginx geconfigureerd zijn, is het tijd om beide webservers te (her)starten. Omdat Apache nog actief is op poort 80, en nginx daardoor nog niet gestart kan worden, moeten we Apache eerst herstarten zodat het op poort 8080 gaat luisteren:

/etc/init.d/httpd restart

Of:

service httpd restart

Vervolgens starten we nginx:

/etc/init.d/nginx start

Of:

service nginx start

Alle verzoeken voor statische bestanden worden nu direct door nginx behandeld. De rest wordt naar Apache doorgestuurd.

Resultaten

Met behulp van testsoftware zoals ApacheBench (ab) kunnen de prestaties eventueel getest worden. Een benchmark van een statisch Javascriptbestand leverde de volgende resultaten op. De benchmark werd uitgevoerd met het commando ab -n 20000 -c 10 http://www.example.com/script.js.

Apache:

Server Software: Apache/2.2.15
Server Hostname: www.example.com
Server Port: 80

Document Path: /script.js
Document Length: 18672 bytes

Concurrency Level: 10
Time taken for tests: 4.116 seconds
Complete requests: 20000
Failed requests: 0
Write errors: 0
Total transferred: 378737870 bytes
HTML transferred: 373477344 bytes
Requests per second: 4859.04 [#/sec] (mean)
Time per request: 2.058 [ms] (mean)
Time per request: 0.206 [ms] (mean, across all concurrent requests)
Transfer rate: 89858.44 [Kbytes/sec] received

Nginx:

Server Software: nginx/1.6.1
Server Hostname: www.example.com
Server Port: 80

Document Path: /script.js
Document Length: 18672 bytes

Concurrency Level: 10
Time taken for tests: 3.315 seconds
Complete requests: 20000
Failed requests: 0
Write errors: 0
Total transferred: 378700000 bytes
HTML transferred: 373440000 bytes
Requests per second: 6034.02 [#/sec] (mean)
Time per request: 1.657 [ms] (mean)
Time per request: 0.166 [ms] (mean, across all concurrent requests)
Transfer rate: 111576.40 [Kbytes/sec] received