Webserver-Optimierung
In den letzten Tagen gab es einige Aussetzer auf diesem Server und sie hängen fast alle mit der Umstellung und Neukonfiguration des Servers zusammen, die ich am Sonntag vollzogen habe. Bislang war das treibende Betriebssystem hier debian sarge (3.1). Da der support für diese recht stabile aber inzwischen in die Jahre gekommene Version von debian bis Ende März eingestellt wird und weil meine Installation einige eigenmächtige Hacks enthielt, habe ich mich entschieden, eine komplette Neuinstallation zu wagen1. Entschieden habe ich mich dann für die letzte Ubuntu Serverversion (7.10 gutsy). Die Konfiguration war allerdings nicht sehr gradlinig..
Anforderungen
Zunächst einmal die Anforderungen an den Server:
Er beherbergt etwa fünf wordpress-installationen, jede mit eigenen (im Allgemeinen recht umfangreichen) plugins und sowohl einer datenbankgestützten, als auch einer log-dateien-gestützten Zugriffsstatistik. Weiterhin soll eine postnuke-Seite2 mit mäßigen Zugriffszahlen, eine interne Bibliotheksverwaltung, eine bis zwei Feed-Aggregator-Seiten und ein internes Forum beherbergen (smf). Daneben sollen mehrere Testumgebungen für diverse weitere Seiten Platz finden und - in Zukunft - eine rechenintensive wiki-Software. Die Datenbanken für diese Seiten sollen alle auf dem Server liegen.
Neben der Webserver-Tätigkeit sollte der Mailverkehr an alle Domains durch den Server selbst geregelt werden und es sollte ein guter Spam-Filter eingebaut sein.
"Hardware"
Der Server ist ein bei Gehrkens.IT in Hannover gehosteter x|pro 256 mit Zugriff auf einen dualen Prozessor und einem Arbeitsspeicher von 256MB.
Software
Neben der bereits angesprochenen Minimalinstallation von Ubuntu3 habe ich die mir bekannte Maillösung Postfix/Courier wieder installiert. Postfix kümmert sich um den Transport der Emails, während Courier das Abrufen der in der mailbox gespeicherten Email sowohl über imap, als auch über pop3 erlaubt.
Was den Webserver angeht, so hatte ich schon früher Probleme mit dem standardmäßig installieten apache(2). Dieser kommt bei den Anforderungen mit den zur Verfügung gestellten 256 mb RAM nicht aus, ist an sich schon recht langsam bei der Verarbeitung von php-Seiten und wird durch das zwingend notwendige Auslagern um so langsamer. Stattdessen habe ich den als "lighty" bekannten lighttpd installiert, der weit mehr als apache auf performance getrimmt ist. Als SQL-Server dient der Webstandard Mysql und als php-interpret die Version php5 in der cgi-Version4.
Konfiguration
Dankenswerter weise sind alle benötigten Pakete Bestandteil der Ubuntu-Distribution5. Ich habe die nachfolgenden Konfigurationsschritte nach Server sortiert, aber in der Praxis mussten sie ineinandergreifen..
Email-Server
Bei der Konfiguration des Mailservers bin ich weitestgehend der Anleitung in dieser howtoforge-Anleitung gefolgt. Dort werden die grundsätzlichen Schritte zur Installation von Postfix inklusive SMTP-AUTH, TLS-Authentifizierung und der dazu gehörigen Courier-Installation erklärt. Geändert habe ich nur zwei Dinge:
- Um Email für mehrere Domainnamen zu erhalten muss die Variable "mydestination" in /etc/postfix/main.cf geändert werden.
CODE:
-
mydestination = my.servername.de, localhost, der.de, die.de
Postfix kann dann allerdings nicht zwischen den Domainnamen unterscheiden und behandelt alle eintreffenden Emails als wären sie an ein und dieselbe Domain gerichtet. Um mehrere Domainnamen mit teilweise ähnlichen Emailnamen6 einzurichten, muss eine zusätzliche Konfiguration virtueller Server erfolgen.
CODE:-
mydestination = my.servername.de, localhost
-
virtual_alias_domains = der.de, die.de
Nun weiß Postfix, dass es Emails für die Domains der.de und die.de annehmen soll, man kann ihm dann noch mitteilen, wie er die Emails zu sortieren hat. Dazu wird eine Datei erstellt, die die Auflösung zwischen Emailadresse und lokalen Benutzern vollzieht:
/etc/postfix/virtual_domains
CODE:-
der@der.de deradmin
-
der@die.de dieadmin
Von dieser Datei sollte noch die hash-Tabelle erstellt werden:
CODE:-
postmap /etc/postfix/virtual_domains
Dieser Befehl sollte die Datei virtual_domains.db erstellen. Danach wird die Datei in der main.cf eingetragen (nicht die .db-Datei!):
CODE:-
virtual_alias_maps = hash:/etc/postfix/virtual_domains
-
- Das nächste ist die Abwehr von Spam-Emails: Postfix bietet mehrere Methoden und Eingriffsmöglichkeiten, um bewerten zu können, ob eine Email 'vernünftig'7 ist. Dazu sollte man wissen, wie die Übermittlung einer Email vonstatten geht8:
- Der Client stellt eine Verbindung zum Mailserver her. Meist wird die Verbindung über den smtp-Port 25 hergestellt, kann aber von server zu server variieren9. Läuft der Server ordnungsgemäß, dann antwortet er mit dem code 220.
- Der Client muss nun mit dem Befehl HELO (oder EHLO10 ) sich identifizieren (also ehlo localhost z.B.) Der Server antwortet mit dem code 250.
- Der "MAIL FROM"-Befehl gibt an, von welchem Absender die Email kommt (MAIL FROM: der@der.de). Wieder antwortet der Server mit 250.
- Der "RCPT TO"-Befehl gibt an, welche Zieladresse die Email hat (RCPT TO: der@die.de). Serverantwort ist wieder 250.
- Dann kommt die eigentliche Übermittlung der Email (inklusive header). Diese wird mit dem Befehl DATA eingeleitet und mit einem Escape-Charakter abgeschlossen, der vom Server beim Eingeben von "DATA" vorgegeben wird.
- Zum Beenden der Verbindung wird der Befehl QUIT ausgeführt.
Eine smtp-Sitzung kann also wie folgt aussehen:
CODE:-
telnet localhost 25
-
Trying 127.0.0.1...
-
Connected to localhost.
-
Escape character is '^]'.
-
220 servername ESMTP Postfix (Ubuntu)
-
ehlo localhost
-
250-servername
-
250-PIPELINING
-
250-SIZE 10240000
-
250-VRFY
-
250-ETRN
-
250-STARTTLS
-
250-AUTH PLAIN LOGIN
-
250-AUTH=PLAIN LOGIN
-
250-ENHANCEDSTATUSCODES
-
250-8BITMIME
-
250 DSN
-
MAIL FROM:der@der.de
-
250 2.1.0 Ok
-
RCPT TO:der@die.de
-
250 2.1.5 Ok
-
DATA
-
354 End data with <CR><LF>.<CR><LF>
-
test
-
.
-
250 2.0.0 Ok: queued as C868A114C84
-
quit
-
221 2.0.0 Bye
-
Connection closed by foreign host.
Mit Postfix hat man die Möglichkeit nach verschiedenen Merkmalen Emails zu klassifizieren. Zur Filterung von Emails gibt es fünf Anknüpfungspunkte:
- smtpd_client_restrictions wird beim Verbindungsaufbau durchlaufen
- smtpd_helo_restrictions wird nach dem helo/ehlo-Befehl durchlaufen
- smtpd_sender_restrictions wird nach dem 'MAIL FROM'-Befehl abgearbeitet
- smtpd_recipient_restrictions wird nach dem 'RCPT TO'-Befehl abgearbeitet
- smtpd_data_restrictions wird nach dem 'DATA'-Befehl abgearbeitet
An jeder dieser Stellen kann eine Email abgelehnt werden. Es hat sich eingebürgert, die Filterung auf den Zeitpunkt nach dem 'RCPT TO'-Befehl zu verschieben11, da einige Mail-Clients ein vorheriges Ablehnen strikt ignorieren und ihre Daten weiterschicken. Da aber bis zum Senden des DATA-Befehls keine übermäßigen Datenmengen zu verarbeiten sind, sollte diese kurze Verzögerung kein Problem darstellen.
Die zur Verfügung stehenden 'restrictions' finden sich auf den Seiten von Postfix. Ich will in Kürze die von mir gewählte Konfiguration erklären:
CODE:-
smtp_delay_reject = yes
-
# keine client_restrictions, die Verbindung soll immer aufgebaut werden dürfen
-
#smtpd_client_restrictions =
-
# Der helo/ehlo-Befehl soll immer ausgeführt werden. Fehlerhafte Verbindungen sollen nicht toleriert werden (das erspart bereits einiges an spam)
-
smtpd_helo_required = yes
-
smtpd_helo_restrictions
-
# Alle Verbindungen vom eigenen Server sollen zugelassen und von den weiteren Prüfungen ausgenommen werden
-
permit_mynetworks,
-
# spezielle hash-Tabelle über helo-Einträge, die nicht zugelassen werden
-
check_helo_access hash:/etc/postfix/helo_access,
-
# Nicht auflösbare und fehlerhafte hostnames ablehnen
-
reject_non_fqdn_hostname,
-
reject_invalid_hostname,
-
# alles andere erst einmal erlauben
-
permit
-
smtpd_sender_restrictions =
-
# Wenn Client sich richtig authentifiziert, soll er senden dürfen
-
permit_sasl_authenticated,
-
# Alle Verbindungen vom eigenen Server sollen zugelassen und von den weiteren Prüfungen ausgenommen werden
-
permit_mynetworks,
-
# Nicht auflösbare Senderadresse nicht zulassen
-
reject_non_fqdn_sender,
-
# Anhand einer manuell erstellten Liste von nicht-erlaubten Absender prüfen (funktioniert nicht, so wie ich möchte...)
-
check_sender_access hash:/etc/postfix/blockedemails
-
# alles andere erst einmal erlauben
-
permit
-
smtpd_recipient_restrictions =
-
# wenn ein client seine daten versucht einzuschleusen, bevor die Empfangsbestätigung vom Server kommt, wird er abgelehnt (fehlerhaftes Verhalten)
-
reject_unauth_pipelining,
-
# nicht auflösbare Empfängeradresse nicht zulassen
-
reject_non_fqdn_recipient,
-
# alle Verbindungen aus dem eigenen Netz erlauben
-
permit_mynetworks,
-
# authentifizierte Verbindungen erlauben und unauthentifizierte Ziele nicht zulassen
-
permit_sasl_authenticated,
-
reject_unauth_destination,
-
# alles andere erlauben
-
permit
Das hält einiges an fehlerhaft konfigurierten Spam-Sendern ab, allerdings reicht das noch lange nicht.
Zur weiteren Minderung trägt die Greylisting-Methode bei. Hierbei wird jedem client der erste Versuch eine Email abzuschicken temporär abgelehnt12. Spam-Versender haben die Angewohnheit dieselbe Email nicht noch einmal abzuschicken. Ordentliche Mailserver würden die Email zurückhalten und in einiger Zeit13 noch einmal abzuschicken versuchen. Beim zweiten Versuch wird dann die Email durchgelassen und der Absender wird für die weitere Zustellung freigestellt.
Das greylisting ist derzeit noch stark umstritten, da die Hürde für Spam-Versender relativ niedrig ist14 und der Nachteil durch die erstmalige Verzögerung einer 'ordentlichen' Email teilweise gravierend sein kann. Einige Administratoren drehen der Methode den Rücken, aufgrund der bei mir immer noch sehr hohen Zahlen an Emails, die durch das greylisting entfernt werden und weil ich grundsätzlich ein Problem mit blacklisting habe15, da ich einem fremden Server Vertrauen schenken muss und dieses durchaus gewollt oder ungewollt missbraucht werden kann, setze ich erst einmal weiterhin greylisting ein.
Für Postfix gibt es das Modul Postgrey, mit dem sich greylisting umsetzen lässt. Unter Ubuntu lässt sich das mit
CODE:-
apt-get install postgrey
einfach installieren. Zur Konfiguration stehen auf der einen Seite whitelist-Dateien unter /etc/postgrey/ zur Verfügung, auf der anderen Seite die Aufrufparameter in der Datei /etc/default/postgrey. In beiden Fällen kann der Default-Zustand beibehalten werden. Damit postfix das Modul anspricht, muss der entsprechende Eintrag in einer der restrictions eingetragen werden:
CODE:-
smtpd_recipient_restrictions =
-
# wenn ein client seine daten versucht einzuschleusen, bevor die Empfangsbestätigung vom Server kommt, wird er abgelehnt (fehlerhaftes Verhalten)
-
reject_unauth_pipelining,
-
# nicht auflösbare Empfängeradresse nicht zulassen
-
reject_non_fqdn_recipient,
-
# alle Verbindungen aus dem eigenen Netz erlauben
-
permit_mynetworks,
-
# authentifizierte Verbindungen erlauben und unauthentifizierte Ziele nicht zulassen
-
permit_sasl_authenticated,
-
reject_unauth_destination,
-
# greylisting
-
check_policy_service inet:127.0.0.1:60000,
-
# alles andere erlauben
-
permit
Weiterhin habe ich Spamassassin installiert, um den restlichen Spam zu beseitigen. Spamassassin ist als Ubuntu-Paket erhältlich und muss nach der Installation16 in den Arbeitsablauf von Postfix eingebunden werden. Dazu wird die Datei /etc/postfix/master.cf editiert. An das smtpd-Modul müssen folgende Argumente übergeben werden:
CODE:-
smtp inet n - - - - smtpd
-
-o content_filter=spamassassin
Dann muss eine zusätzliche Zeile eingefügt werden:
CODE:-
spamassassin unix - n n - - pipe
-
user=spamfilter argv=/usr/bin/spamc -f -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}
Der Benutzer 'spamfilter' muss angelegt werden, dann startet man postfix neu und schon funktioniert spamassassin!
Webserver
Ein großer Vorteil von lighttpd ist die Art, wie es Skriptsprachen wie php einbindet. Während Apache standardmäßig bei jedem Aufruf eines php-skripts eine neue php-Instanz startet und nach erfolgter Auswertung die Instanz wieder beendet, hält lighttpd über die fastcgi-Schnittstelle mehrere Instanzen von php-cgi auf Abrufbereitschaft und verteilt auf diese offenen Instanzen die anliegenden Auswertearbeiten. Die kostspieligen Starts und Stops entfallen somit komplett17.
Zwischendurch hatte ich sowohl fcgi als auch das 'normale' cgi aktiviert. Was dann geschah, hat mich mehr als zwei Stunden meines Lebens gekostet: Lighttpd startete wie gewohnt einen php-cgi-Prozess, der auf eine Anfrage gewartet hat. Wenn dann eine Anfrage an Lighttpd kam, wurde eine weitere php-cgi-Instanz gestartet und nach erfolgter Verarbeitung wieder geschlossen. Durch die umsonst geöffneten Instanzen kam es dann regelmäßig zur totalen Speicherauslastung und der Server war überhaupt nicht mehr erreichbar.. Offensichtlich geniesst cgi eine höhere Priorität als fast-cgi bei der Verteilung der Aufgaben.
Zur weiteren Beschleunigung von php-Skripten habe ich dann zum Schluß noch xcache installiert. Xcache speichert den kompilierten Zwischenstand einzelner php-skripte. Dadurch wird das erneute Kompilieren beim nochmaligen Aufruf erspart. Dies hat dem Seitenaufbau um wenige Millisekunden weitergeholfen18. Weiterhin bietet xcache einen Variablenspeicher an für Variablen, die sich nicht ganz so schnell ändern. Dieser Speicher muss allerdings einzeln von den php-skripten angesprochen werden, wofür mehr Programmierbedarf da ist. Glücklicherweise hat mein Cousin eine Erweiterung für wordpress geschrieben, die einige Datenbankabfragen in xcache zwischenspeichert. Dadurch reduzieren sich die Datenbankabfragen bei einem Aufruf der Seite teilweise um die Hälfte!
Ende
Ich hoffe, diese recht lang ausgefallene Zusammenfassung kann einigen verzweifelnden Administratoren einiges an Arbeit abnehmen oder in die richtige Richtung verweisen. Mir dient sie als Gedankenstütze und ich hoffe, ich kann hierauf aufbauend auch in der Zukunft Tips aus der eigenen Erfahrung bei der Administratotion dieses Servers aufschreiben. Viel Spaß!
- ja, Linux-profis werden entgegnen, man installiere Linux nie neu; aber bei der verqueren Installationslage des Systems hier auf dem Server, hätte ich mir mit einem dist-upgrade jeder Art zu viel Ärger eingehandelt. Schuldig bleibe ich ja trotzdem.. [↩]
- demnächst als drupal-Seite umgesetzt, aber da bin ich mir noch nicht ganz sicher... [↩]
- xencon bietet ein "ubuntu feisty"-image, das ich auf gutsy gibbon dist-upgraded habe. Dies habe ich vor der Installation weiterer Software, aber nach einem grundlegenden apt-get update und apt-get upgrade gemacht. [↩]
- apt-get install php5-cgi [↩]
- bei sarge musste ich noch so einige Pakete selbst zusammensuchen oder vom Quelltext erstellen lassen [↩]
- also z.B. der@der.de und der@die.de, wenn der.de und die.de von einem einzigen server verwaltet werden [↩]
- von einem 'vernünftigen' Rechner stammend, von einem 'vernünftigen' Sender gesendet, an einen 'vernünftigen' Empfänger gerichtet und mit einem 'vernünftigen' Inhalt gefüllt [↩]
- dies habe ich mir auch nur 'hier und da' erarbeitet, deshalb können hier durchaus Fehler enthalten sein.. [↩]
- Bei Postfix wird der 'listen'-Port in /etc/postfix/master.cf eingerichtet. Um einen weiteren Port einzurichten, kann man die erste nicht-auskommentierte Zeile duplizieren und den ersten Wert (normalerweise smtp = 25) auf die gewünschte Portadresse setzen [↩]
- Beim Befehl EHLO antwortet der Server mit einer erweiterten Befehlsliste, um dem client zu signalisieren, welche SMTP-Befehle es zusätzlich zu den Standardbefehlen verstehen kann [↩]
- Für Postfix ist es die Konfiguration 'smtp_delay_reject = yes' [↩]
- Die Verschiedenen SMTP-Fehlercodes gibt es z.B. hier. Zum greylisting wird meist der code 450 oder 451 abgeschickt [↩]
- nicht standardisiert [↩]
- einen ordentlich versendenden Server zu halten sollte nich so schwierig sein [↩]
- als Blacklists bezeichnet man Listen, die von einigen Servern zur Verfügung gestellt werden, in denen Emailversender eingetragen werden, die derzeit Spam verschicken. [↩]
- apt-get install spamassassin [↩]
- fastcgi ist eigentlich auch für Apache implementiert, wird aber nicht standardmäßig eingebunden. Lighttpd hat aber weitere Vorteile, vor allem was den Speicherverbrauch angeht.. [↩]
- 0.3 Sekunden braucht der Seitenaufbau von toomuchcookies.net normalerweise. Mit xCache ist es nur noch ein Zehntel einer Sekunde! [↩]