Konfigurieren eines minimalen Apache Servers


Konfigurieren eines minimalen Apache Servers

Title: Apache minimal Konfigurieren
Author: Christian Folini
Tutorial Nr: 2
Letztes Update: 28.10.2019
Erscheinungsdatum: 06.11.2010
Schwierigkeit: Leicht
Dauer: 1/2h

Was machen wir?

Wir konfigurieren einen minimalen Apache Webserver und sprechen ihn mit curl, der TRACE-Methode und ab an.

Warum tun wir das?

Ein sicherer Server ist ein Server, der nur soviel zulässt, wie wirklich benötigt wird. Idealerweise baut man einen Server also auf Basis eines minimalen Systems auf, indem man weitere Features nacheinander einzeln zuschaltet. Dies ist auch aus Verständnisgründen vorzuziehen, denn nur in diesem Fall versteht man, was wirklich konfiguriert ist. Ferner ist es bei der Fehlersuche hilfreich, von einem minimalen System auszugehen. Ist der Fehler im minimalen System noch nicht vorhanden, werden die Features einzeln zugeschaltet und neu nach dem Fehler gesucht. Sobald er auftaucht, ist es klar, dass er mit der zuletzt zugeschalteten Konfigurationsdirektive in Verbindung steht.

Voraussetzungen

Schritt 1: Minimale Konfiguration erstellen

Unser Webserver ist auf dem Dateisystem unter /apache abgelegt. Unter /apache/conf/httpd.conf liegt seine Standard-Konfiguration. Diese ist sehr umfangreich und nur schwer zu verstehen. Aber zumindest ist alles noch in einer einzigen Datei. Für die Apache Versionen in der verschiedenen Linux-Distributionen ist die Standardkonfiguration nicht nur sehr kompliziert, sie ist auch in verschiedenste separate Dateien fragmentiert. Diese sind über mehrere Verzeichnisse verteilt. Dies kann es schwierig machen, einen guten Überblick darüber zu bekommen, was tatsächlich vor sich geht. Zur Vereinfachung werden wir diese umfangreiche Konfigurationsdatei durch die folgende, stark vereinfachte Konfiguration ersetzen.

ServerName              localhost
ServerAdmin             root@localhost
ServerRoot              /apache
User                    www-data
Group                   www-data
PidFile                 logs/httpd.pid

ServerTokens            Prod
UseCanonicalName        On
TraceEnable             Off

Timeout                 10
MaxRequestWorkers       100

Listen                  127.0.0.1:80

LoadModule              mpm_event_module        modules/mod_mpm_event.so
LoadModule              unixd_module            modules/mod_unixd.so

LoadModule              log_config_module       modules/mod_log_config.so

LoadModule              authn_core_module       modules/mod_authn_core.so
LoadModule              authz_core_module       modules/mod_authz_core.so

ErrorLogFormat          "[%{cu}t] [%-m:%-l] %-a %-L %M"
LogFormat               "%h %l %u [%{%Y-%m-%d %H:%M:%S}t.%{usec_frac}t] \"%r\" %>s %b \
\"%{Referer}i\" \"%{User-Agent}i\"" combined

LogLevel                debug
ErrorLog                logs/error.log
CustomLog               logs/access.log combined

DocumentRoot            /apache/htdocs

<Directory />
      
    Require all denied

    Options SymLinksIfOwnerMatch

</Directory>

<VirtualHost 127.0.0.1:80>
      
      <Directory /apache/htdocs>

        Require all granted

        Options None

      </Directory>

</VirtualHost>

Schritt 2: Konfiguration verstehen

Gehen wir diese Konfiguration Schritt für Schritt durch.

Wir setzen den ServerName auf Localhost, weil wir immer noch an einem Laborsetup arbeiten. Für die Produktion ist hier der voll qualifizierte Hostname des Services einzutragen. Kurz: Umgangssprachlich die URL.

Der Server benötigt vor allem für die Darstellung der Fehlerseiten eine Emailadresse des Administrators. Sie wird mit dem ServerAdmin gesetzt.

Das ServerRoot Verzeichnis bezeichnet das Haupt- oder Wurzelverzeichnis des Servers. Es ist der in Anleitung 1 als Kniff gesetzte Symlink. Dies kommt uns nun zugute, denn durch Umlegen dieses Symlinks können wir nebeneinander verschieden kompilierte Apache-Versionen ausprobieren, ohne an der Konfigurationsdatei etwas verändern zu müssen.

Dann weisen wir dem Server mit User und Group den Benutzer und dessen Gruppe zu. Dies ist sinnvoll, denn wir möchten vermeiden, dass der Server als Root-Prozess läuft. Vielmehr wird der Master- bzw. Parent-Prozess als Root laufen, aber die eigentlichen Server- bzw. Child-Prozesse und deren Threads laufen unter dem hier gesetzten Namen. Der User www-data ist der unter einem Debian-/Ubuntu-System übliche Name. Andere Distributionen verwenden andere Namen. Bitte stellen Sie sicher, dass der von Ihnen gewählte Username und die zugehörige Gruppe auf dem System auch tatsächlich vorhanden ist.

Das PidFile gibt an, in welches File Apache seine Prozess-ID Nummer schreiben soll. Der gewählte Pfad entspricht dem Defaultwert. Er wird hier angeführt, damit man später nicht in der Dokumentation nach diesem Pfad zu suchen braucht.

ServerTokens definiert die Selbstbezeichnung des Servers. Produktive Tokens werden mit Prod festgelegt. Dies bedeutet, dass sich der Server nur als Apache und nicht auch noch mit Versionsnummer und geladenen Modulen ausweist und sich damit etwas diskreter gibt. Machen wir uns keine Illusionen: Die Serverversion lässt sich über das Internet mit wenig Aufwand feststellen, aber wir brauchen es ja trotzdem nicht gerade bei jeder Kommunikation als Teil des Absenders mitzuschicken.

UseCanonicalName teilt dem Server mit, welchen Hostnamen und welchen Port er verwenden soll, wenn er einen Link auf sich selbst zu schreiben hat. Mit dem Wert On bestimmen wir, dass der ServerName zu verwenden ist. Eine Alternative wäre es, den vom Client gesendeten Host-Header zu verwenden, was wir aber in unserem Setup nicht möchten.

Die TraceEnable-Direktive verhindert gewisse Spionageattacken auf unseren Setup. Die HTTP Methode TRACE instruiert den Webserver, die von ihm erhaltene Anfrage 1:1 zu retournieren. Dies erlaubt es festzustellen, ob ein Proxy-Server zwischengeschaltet ist und ob dieser den Request verändert hat. In unserem simplen Setup ist damit noch nichts verloren, aber in einem Unternehmensnetz möchte man diese Informationen lieber geheim halten. Schalten wir TraceEnable also sicherheitshalber per Default aus.

Timeout bezeichnet grob gesagt die Zeit in Sekunden, welche für die Verarbeitung eines Requests maximal verwendet werden darf. Tatsächlich verhält es sich damit etwas komplizierter, aber die Details brauchen uns für den Moment nicht zu interessieren. Der Standard-Wert ist mit 60 Sekunden sehr hoch. Wir reduzieren ihn auf 10 Sekunden.

MaxRequestWorkers ist die maximale Anzahl Threads, welche parallel an der Beantwortung von Anfragen arbeiten. Der Standard-Wert ist wieder etwas hoch. Setzen wir ihn auf 100. Sollten wir diesen Wert in der Produktion erreichen, haben wir schon recht viel Verkehr.

Standardmässig hört der Apache Server auf jeder verfügbaren Adresse ins Netz. Für unsere Tests lassen wir ihn aber erst mal nur auf der IPv4 Localhost Adresse und auf dem Standard-HTTP-Port 80 lauschen. Mehrere Listen-Direktiven nacheinander sind problemlos möglich; für uns reicht im Moment eine einzige.

Nun laden wir fünf Module:

  • mpm_event_module : Prozessmodell "event"
  • unixd_module : Zugriff auf Unix Usernamen und Gruppen
  • log_config_module : Freie Definition des Zugriffs- / Access-Logs
  • authn_core_module : Basismodul für die Authentifizierung
  • authz_core_module : Basismodul für die Autorisierung

Wir hatten in der Lektion 1 ja alle mitgelieferten Module vorkompiliert. Hier nehmen wir nur die wichtigsten in unsere Konfiguration auf. _mpm_eventmodule und _unixdmodule sind nötig für den Betrieb des Servers. Bei der Kompilierung im ersten Tutorial hatten wir uns für das Prozessmodell event entschieden, das wir hier nun durch das Laden des Moduls aktivieren. Interessant: Bei Apache 2.4 lässt sich auch eine so grundlegende Einstellung wie das Prozessmodell des Servers mittels der Konfiguration auswählen. Das Modul unixd benötigen wir, um den Server, wie oben beschrieben, unter dem von uns definierten Usernamen laufen zu lassen.

Das Log-Modul _log_configmodule erlaubt uns eine freie Definition des Access-Logs, wovon wir im Folgenden gleich Gebrauch machen werden. Schliesslich die beiden Module _authn_coremodule und _authz_coremodule. Der erste Teil des Namens verweist auf Authentisierung (Authn) und Autorisierung (Authz). Core bedeutet dann, dass es sich bei diesen Modulen um die Basis für diese Funktionen handelt.

Beim Zugriffsschutz spricht man oft von AAA, also Authentisierung, Autorisierung und Access Control. Authentisieren bedeutet dabei das Überprüfen der Identität eines Benutzers. Unter Autorisierung versteht man das Feststellen der Zugriffsrechte eines vorher authentisierten Benutzers. Access Control schliesslich bedeutet die Entscheidung, ob ein authentisierter Benutzer mit den eben festgestellten Zugriffsrechten zugelassen wird. Die Basis für diesen Mechanismus legen wir durch das Laden dieser beiden Module. Alle weiteren Module mit den beiden Kürzeln authn und authz, von denen es eine grosse Menge gibt, setzen diese Module voraus. Für den Moment benötigen wir eigentlich nur das Autorisierungsmodul, aber mit dem Laden des Authentisierungsmoduls bereiten wir uns auf spätere Erweiterungen vor.

Mit ErrorLogFormat greifen wir in das Format des Fehler-Logfiles ein. Wir erweitern das gängige Logformat etwas, indem wir namentlich den Zeitstempel sehr genau definieren. [%{cu}t] entspricht damit einem Eintrag wie [2015-09-24 06:34:29.199635]. Das heisst das Datum rückwärts notiert, dann die Uhrzeit mit einer Genauigkeit von Mikrosekunden. Die Umkehrung des Datums hat den Vorteil, dass sich die Zeiten im Logfile sauber ordnen lassen; die Mikrosekunden geben uns genaue Auskunft über den Zeitpunkt eines Eintrages und lassen gewisse Rückschlüsse über die Zeitdauer der Verarbeitung in verschiedenen Modulen zu. Dem dient auch der nächste Konfigurationsteil [%-m:%-l] , der das loggende Modul und den Loglevel, also die Schwere des Fehlers nennt. Danach folgen die IP-Adresse des Clients ( %-a); eine eindeutige Identifikation des Requests (%-L); eine sogenannte Unique-ID, welche in späteren Anleitungen zur Korrelation von Requests dienen kann) und schliesslich die eigentliche Meldung, die wir mittels %M referenzieren.

Mit LogFormat definieren wir ein Format für das Zugriffs-Logfile. Wir nennen es combined. Dieses gängige Format schliesst Client-IP-Adresse, Zeitstempel, Methode, Pfad, HTTP-Version, HTTP-Status-Code, Antwort-Grösse, Referer und die Bezeichnung des Browsers (User-Agent) mit ein. Beim Zeitstempel wählen wir eine recht komplizierte Konstruktion. Der Grund ist der Wille, beim Error-Log und im Access-Log die Timestamps in demselben Format anzeigen zu können. Während wir dazu im Error-Log aber eine einfache Identifikation haben, müssen wir den Zeitstempel im Falle des Access-Log-Formats mühsam konstruieren.

Den LogLevel für das Fehler-Logfile stellen wir mit Debug auf die höchste Stufe. Das ist für die Produktion zu gesprächig, im Labor macht das aber durchaus Sinn. Apache ist gemeinhin nicht sehr gesprächig, so dass man mit der Datenmenge meist gut zurecht kommt.

Dem Fehler-Logfile weisen wir mit ErrorLog den Pfad logs/error.log zu. Dieser Pfad ist relativ zum ServerRoot-Verzeichnis.

Das definierte LogFormat combined benützen wir nun für unser Zugriffs-Logfile namens logs/access.log.

Der Webserver liefert Dateien aus. Diese sucht er auf einer Diskpartition, oder er generiert sie mithilfe einer installierten Applikation. Wir sind noch beim einfachen Fall und geben dem Server mittels DocumentRoot bekannt, wo er die Dateien findet. /apache/htdocs ist ein absoluter Pfad unter dem ServerRoot. Hier könnte auch wieder ein relativer Pfad stehen, aber arbeiten wir hier besser mit klaren Verhältnissen! Konkret bedeutet DocumentRoot, dass der URL-Pfad / auf den Betriebssystempfad /apache/htdocs gemappt wird.

Nun folgt ein Directory-Block. Mit diesem Block verhindern wir, dass Dateien ausserhalb des von uns bezeichneten DocumentRoot ausgeliefert werden. Für den Pfad / verbieten wir jeglichen Zugriff mittels der Direktiven Require all denied. Dieser Eintrag referenziert die Authentifizierung (all), macht eine Aussage zur Autorisierung (Require) und definiert den Zugriff: denied, also gar keinen Zugriff und zwar für niemanden; jedenfalls nicht für das Verzeichnis /.

Die Direktive Options setzen wir auf SymLinksIfOwnerMatch. Mit Options können wir festlegen welche Spezialfeatures beim Ausliefern des Verzeichnisses / beachtet werden sollen. Eigentlich gar keine und in der Produktion würden wir deshalb Options None schreiben. In unserem Fall haben wir aber das DocumentRoot auf einen symbolischen Link gelegt und der wird nur dann gesucht und auch gefunden, wenn wir den Server mit SymLinksIfOwnerMatch anweisen, unterhalb von / auch Symlinks zuzulassen. Zumindest wenn die Besitzverhältnisse sauber sind. Auf produktiven Systemen ist aus Sicherheitsgründen beim Servieren von Files besser auf Symlinks zu verzichten. Aber bei unserem Testsystem geht der Komfort noch vor.

Nun eröffnen wir einen VirtualHost. Er korrespondiert mit der oben definierten Listen-Direktive. Zusammen mit dem eben definierten Directory-Block legt er fest, dass unser Webserver per Default gar keinen Zugriff zulässt. Auf der IP-Adresse 127.0.0.1, Port 80 wollen wir aber Zugriffe zulassen und die werden innerhalb dieses Blocks definiert.

Konkret lassen wir Zugriffe auf unser DocumentRoot zu. Schlüsselanweisung ist hier das Require all granted, womit wir im Gegensatz zum Verzeichnis / kompletten Zugriff zulassen. Anders als oben sind ab diesem Pfad nun keine Symlinks mehr vorgesehen und auch sonst keine Spezialfähigkeiten: Options None.

Schritt 3: Server starten

Damit ist unser minimaler Server beschrieben. Es wäre möglich, einen noch knapperen Server zu definieren. Aber damit liesse sich nicht mehr so komfortabel arbeiten wie mit unserem und er wäre auch nicht mehr sicher. Eine gewisse Grundsicherung ist aber angebracht. Denn wenn wir nun im Labor einen Service aufbauen, dann sollte der sich auch mit punktuellen Anpassungen in eine produktive Umgebung verschieben lassen. Einen Service kurz vor der Produktivschaltung noch von Grund auf sichern zu wollen ist illusorisch.

Starten wir den Server wieder wie in Lektion 1 im Vordergrund und nicht als Daemon:

$> cd /apache
$> sudo ./bin/httpd -X

Schritt 4: Server mit curl ansprechen

Wir können den Server nun wieder mit dem Browser ansprechen. Aber aus der Shell heraus lässt es sich erst mal sauberer arbeiten und besser verstehen, was passiert:

$> curl http://localhost/index.html

Dies liefert folgendes Resultat.

<html><body><h1>It works!</h1></body></html>

Wir haben also einen HTTP-Aufruf abgesetzt und von unserem minimal konfigurierten Server eine Antwort erhalten, die unseren Erwartungen entspricht.

Schritt 5: Anfrage und Antwort untersuchen

Das passiert also bei einer HTTP-Anfrage. Aber was antwortet der Server uns eigentlich genau? Dazu rufen wir curl nochmals auf. Dieses Mal mit der Option verbose.

$> curl --verbose http://localhost/index.html
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /index.html HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Thu, 24 Sep 2015 09:27:02 GMT
* Server Apache is not blacklisted
< Server: Apache
< Last-Modified: Mon, 11 Jun 2007 18:53:14 GMT
< ETag: "2d-432a5e4a73a80"
< Accept-Ranges: bytes
< Content-Length: 45
< 
<html><body><h1>It works!</h1></body></html>
* Connection #0 to host localhost left intact

Die mit einem * bezeichneten Zeilen beschreiben Meldungen zum Aufbau und Abbau der Verbindung. Sie geben keinen Netzwerkverkehr wieder. Dann folgt mit > die Anfrage und mit < die Antwort.

Konrekt besteht eine HTTP-Anfrage aus 4 Teilen:

  • Request-Zeile und Request-Header
  • Request-Body (optional und hier bei einem GET-Request fehlend)
  • Response-Header
  • Response-Body

Die ersten Teile brauchen hier noch nicht zu interessieren. Interessant sind die Response-Header. Das ist der Teil, mit dem der Webserver die Antwort beschreibt. Die eigentliche Antwort, der Response-Body, folgt dann nach einer Leerzeile.

Was sagen die Header nacheinander?

Zunächst folgt die Status-Zeile mit dem Protokoll inklusive der Version, dann folgt der Status-Code. 200 OK ist die normale Antwort eines Webservers. Auf der nächsten Zeile sehen wir das Datum und die Uhrzeit des Servers. Die anschliessende Zeile beginnt mit einem Stern, *, und bezeichnet damit eine zu curl gehörige Zeile. Die Nachricht hat mit curls Behandlung von HTTP Pipelining zu tun, was uns nicht weiter zu interessieren braucht. Dann folgt die Server-Zeile, auf der sich unser Webserver als Apache identifiziert. Dies ist die knappste mögliche Identifikation. Wir haben sie mit ServerTokens Prod definiert.

Dann teilt der Server mit, wann das der Antwort zu Grunde liegende File zum letzten Mal verändert wurde; also die Unix Modified-Timestamp. ETag und Accept-Ranges brauchen für den Moment nicht zu interessieren. Interessanter ist die Content-Length. Diese gibt an, wieviele Bytes im Response-Body erwartet werden dürfen. In unserem Fall sind das 45 Bytes.

Übrigens ist die Reihenfolge dieser Header charakteristisch für einen Webserver. NginX verwendet eine andere Reihenfolge und bringt den Server-Header beispielsweise vor dem Datum. Apache lässt sich deshalb auch identifizieren, wenn die Server-Zeile uns in die Irre führen sollte.

Schritt 6: Die Antwort noch etwas genauer untersuchen

Es ist möglich, bei der Kommunikation noch etwas tiefer in curl hineinzublicken. Das geschieht über den Kommandozeilen-Parameter --trace-ascii:

$> curl   http://localhost/index.html --trace-ascii -
== Info: Hostname was NOT found in DNS cache
== Info:   Trying 127.0.0.1...
== Info: Connected to localhost (127.0.0.1) port 80 (#0)
=> Send header, 83 bytes (0x53)
0000: GET /index.html HTTP/1.1
001a: User-Agent: curl/7.35.0
0033: Host: localhost
0044: Accept: */*
0051: 
<= Recv header, 17 bytes (0x11)
0000: HTTP/1.1 200 OK
<= Recv header, 37 bytes (0x25)
0000: Date: Thu, 24 Sep 2015 11:46:17 GMT
== Info: Server Apache is not blacklisted
<= Recv header, 16 bytes (0x10)
0000: Server: Apache
<= Recv header, 46 bytes (0x2e)
0000: Last-Modified: Mon, 11 Jun 2007 18:53:14 GMT
<= Recv header, 26 bytes (0x1a)
0000: ETag: "2d-432a5e4a73a80"
<= Recv header, 22 bytes (0x16)
0000: Accept-Ranges: bytes
<= Recv header, 20 bytes (0x14)
0000: Content-Length: 45
<= Recv header, 2 bytes (0x2)
0000: 
<= Recv data, 45 bytes (0x2d)
0000: <html><body><h1>It works!</h1></body></html>.
<html><body><h1>It works!</h1></body></html>
== Info: Connection #0 to host localhost left intact

Der Parameter --trace-ascii benötigt ein File als Parameter, um darin einen Ascii Dump der Kommunikation abzulegen. "-" funktioniert als Shortcut zu STDOUT, so dass wir uns die Mitschrift einfach anzeigen lassen können.

Gegenüber verbose bringt trace-ascii mehr Details zur Länge der übertragenen Bytes in der Request- und Response-Phase. Die Request-Header umfassten in obigem Beispiel also 83 Bytes. Bei der Antwort werden die Bytes dann pro Header-Zeile gelistet und pauschal für den Body der Antwort: 45 Bytes. Das mag jetzt alles nach Haarspalterei klingen. Tatsächlich ist es aber bisweilen spielentscheidend, wenn man ein Stückchen vermisst und sich nicht ganz sicher ist, was wo in welcher Reihenfolge angeliefert wurde. So ist es etwa auffällig, dass bei den Headerzeilen jeweils 2 Bytes hinzukommen. Das sind der CR (Carriage Return) und NL (New Line), den das HTTP-Protokoll in den Header-Zeilen vorsieht. Anders im Response-Body, wo nur das retourniert wird, was tatsächlich in der Datei steht. Das ist hier offensichtlich nur ein NL ohne CR. Auf der drittuntersten Zeile (0000: html ...) folgt auf das grösser-als-Zeichen ein Punkt. Dies ist eine Umschreibung des NL-Charakters der Antwort, der wie andere Escape-Sequenzen auch in der Form eines Punktes wiedergegeben wird.

Schritt 7: Mit Trace Methode arbeiten

Oben habe ich die Direktive TraceEnable beschrieben. Wir haben sie sicherheitshalber auf off geschaltet. Bei der Fehlersuche kann sie aber ganz nützlich sein. Also probieren wir das doch mal aus. Setzen wir die Option auf on:

TraceEnable On

Wir starten den Server neu und setzen folgenden curl-request ab.

$> curl -v --request TRACE http://localhost/index.html

Wir rufen also die bekannte URL mit der HTTP Methode TRACE (anstatt GET) auf. Als Resultat erwarten wir folgendes:

* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 80 (#0)
> TRACE /index.html HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Thu, 24 Sep 2015 09:38:01 GMT
* Server Apache is not blacklisted
< Server: Apache
< Transfer-Encoding: chunked
< Content-Type: message/http
< 
TRACE /index.html HTTP/1.1
User-Agent: curl/7.35.0
Host: localhost
Accept: */*

* Connection #0 to host localhost left intact

Im Body wiederholt der Server wie vorgesehen die Informationen zum gesendeten Request. Tatsächlich sind die Zeilen hier identisch. Wir können also bestätigen, dass unterwegs nichts mit der Anfrage passiert ist. Wenn wir aber über einen oder mehrere Proxy-Server gegangen wären, dann gäbe es hier auf jeden Fall weitere Header-Zeilen, die wir so auch als Client zu Gesicht bekommen können. Zu einem späteren Zeitpunkt werden wir mächtigere Hilfsmittel für die Fehlersuche kennen lernen. Aber ganz ausser Acht lassen sollten wir die TRACE-Methode dennoch nicht.

Vergessen Sie nicht, TraceEnable wieder auszuschalten.

Schritt 8: Server mit "ab" auf den Zahn fühlen

Das wär's erst Mal mit dem simplen Server. Spasseshalber können wir ihm aber noch etwas auf den Zahn fühlen. Wir inszenieren einen kleinen Lasttest mit ab; kurz für Apache Bench. Dies ist ein sehr einfaches Lasttest-Programm, das immer zur Hand ist und rasche erste Resultate zur Performance liefern kann. So lasse ich ab gerne vor und nach einer Konfigurationsänderung laufen, um eine Idee zu erhalten, ob sich an der Performance etwas verändert hat. Ab ist nicht sehr mächtig und der lokale Aufruf bringt auch keine sauberen Resultate. Aber so ein erster Augenschein lässt sich mit diesem Hilfsmittel gewinnen.

$> ./bin/ab -c 1 -n 1000 http://localhost/index.html

Wir starten ab mit concurrency 1. Das heisst, dass wir parallel nur eine einzige Anfrage stellen. Total stellen wir 1000 Anfragen auf die bekannte URL. Hier ist die Ausgabe von ab:

$> ./bin/ab -c 1 -n 1000 http://localhost/index.html
This is ApacheBench, Version 2.3 <$Revision: 1663405 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        Apache
Server Hostname:        localhost
Server Port:            80

Document Path:          /index.html
Document Length:        45 bytes

Concurrency Level:      1
Time taken for tests:   0.676 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      250000 bytes
HTML transferred:       45000 bytes
Requests per second:    1480.14 [#/sec] (mean)
Time per request:       0.676 [ms] (mean)
Time per request:       0.676 [ms] (mean, across all concurrent requests)
Transfer rate:          361.36 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:     0    1   0.2      1       3
Waiting:        0    0   0.1      0       2
Total:          0    1   0.2      1       3

Percentage of the requests served within a certain time (ms)
  50%      1
  66%      1
  75%      1
  80%      1
  90%      1
  95%      1
  98%      1
  99%      1
 100%      3 (longest request)

Interessant ist für uns vor allem die Zahl der Fehler (Failed Requests) und die Zahl der Anfragen pro Sekunde (Request per second). Ein Wert von über Tausend ist ein guter Start. Zumal wir ja immer noch mit einem einzigen Prozess und nicht mit einem parallelisierten Daemon arbeiten (und deshalb auch der concurrency-level auf 1 gesetzt ist).

Schritt 9: Direktiven und Module ansehen

Zum Schluss dieses Tutorials schauen wir uns die verschiedenen Direktiven an, welche ein mit unserem Konfigurationsfile zu startender Apache kennt. Die verschiedenen geladenen Module erweitern den Befehlssatz des Servers. Die damit zur Verfügung stehenden Konfigurationsparameter sind auf der Webseite des Projektes gut dokumentiert. Tatsächlich kann es aber in besonderen Fällen hilfreich sein, den durch die geladenen Module zur Verfügung stehenden Direktiven zu überblicken. Die Direktiven erhält man mit dem Kommando-Zeilen-Flag -L.

$> ./bin/httpd -L
<Directory (core.c)
    Container for directives affecting resources located in the specified directories
    Allowed in *.conf only outside <Directory>, <Files>, <Location>, or <If>
<Location (core.c)
    Container for directives affecting resources accessed through the specified URL paths
    Allowed in *.conf only outside <Directory>, <Files>, <Location>, or <If>
<VirtualHost (core.c)
    Container to map directives to a particular virtual host, takes one or more host addresses
    Allowed in *.conf only outside <Directory>, <Files>, <Location>, or <If>
<Files (core.c)
...

Die Direktiven folgen hierbei der Reihenfolge wie sie geladen werden. Zu jeder Direktive folgt darauf eine kurze Beschreibung der Funktionalität.

Mit dieser Liste ist es nun möglich herauszufinden, ob man sämtliche geladenen Module in der Konfiguration auch wirklich benötigt, respektive referenziert. In komplizierteren Konfigurationen mit zahlreichen geladenen Modulen kann es schliesslich schon mal vorkommen, dass man unsicher ist, ob man alle Module wirklich verwendet.

Man kann die Module also aus dem Konfigurationsfile herauslesen, den Output von httpd -L pro Modul zusammenfassen und dann wiederum im Konfigurationsfile nachsehen, ob eine der gelisteten Direktiven benützt wird. Diese verschachtelte Abfrage ist eine schöne Fingerübung, die ich nur empfehlen kann. Für mich habe ich sie wie folgt gelöst:

$> grep LoadModule conf/httpd.conf | awk '{print $2}' | sed -e "s/_module//" | while read M; \
do echo "Module $M"; R=$(./bin/httpd -L | grep $M | cut -d\  -f1 | tr -d "<" | xargs | \
tr " " "|");  egrep -q "$R" ./conf/httpd.conf; if [ $? -eq 0 ]; \
then echo "OK"; else echo "Not used"; fi; echo; done
Module mpm_event
OK

Module unixd
OK

Module log_config
OK

Module authn_core
Not used

Module authz_core
OK

Das Modul _authncore wird also nicht verwendet. Das ist korrekt; das hatten wir oben auch so beschrieben, denn es ist für eine zukünftige Verwendung geladen. Die übrigen Module scheinen nötig.

Soweit zu diesem Tutorial. Damit ist bereits ein tauglicher Webserver vorhanden, mit dem man gut arbeiten kann. In den nächsten Lektionen bauen wir ihn weiter aus.

Newsletter

Hat dieses Tutorial Spass gemacht? Dann wäre doch unser Newsletter mit Infos zu neuen Artikeln hier bei netnea das Richtige. Hier geht es zum Einschreiben.
Der Newsletter erscheint in englischer Sprache.

Verweise

Lizenz / Kopieren / Weiterverwenden

Creative Commons License
Diese Arbeit ist wie folgt lizenziert / This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Changelog

  • 28. Oktober 2019: Änderungen im deutschen Text gemäss englischem Text nachgezogen
  • 25. Februar 2017: Neu-Formulierung eines Satzes in der Einleitung, AllowOverride komplett entfernt
  • 25. August 2016: Zeilenumbrüche justiert
  • 16. Januar 2016: Lizenzhinweis eingefügt
  • 14. September 2015: Rechtschreibung (Benjamin Affolter)
  • 24. September 2015: Update auf Apache 2.4, Erweiterung um --trace-ascii, httpd -L
  • 21. September 2015: html -> markdown
  • 24. Juli 2013: Rechtschreibfehler
  • 9. Juli 2013: Schritt 3: "cd /apache" vorangestellt; Ausgabe der ab-Aufrufs angepasst
  • 2. Juli 2013: Rechtschreibfehler in Konfiguration korrigiert (DefaultType vs. ContentType), verbose curls call aktualisiert
  • 31. Mai 2013: Rechtschreibfehler korrigiert
  • 9. April 2013: Mime-Modul hinzugefügt, PidFile definiert
  • 27. Februar 2011: Rechtschreibfehler korrigiert
  • 25. Februar 2011: Rechtschreibfehler korrigiert
  • 20. Januar 2011: Überarbeitet
  • 6. November 2010: Überarbeitet
  • 4. und 5. November 2010: Erstellt