Ubuntu Server 14.04 – LAMP-Server installieren und konfigurieren

Basis für viele Webauftritte mit dynamischen Inhalten ist das Softwarepaket LAMP. Der LAMP-Server läuft unter dem Betriebssystem Linux und beinhaltet den Webserver Apache, das Datenbanksystem MySQL und die Skriptsprache PHP.
Im Folgenden werden wir LAMP auf einem Server mit der Linux-Variante Ubuntu Server 14.04 installieren und danach Apache und MySQL für einen sicheren Betrieb anpassen. Hierzu realisieren wir ein restriktives Apache-Sicherheitsmodell und richten eine automatische Umleitung ein, die den Zugriff auf die Standard-Website nur über das verschlüsselnde „HyperText Transfer Protocol Secure“ HTTPS zulässt. Neben dieser Grundabsicherung sind weitergehende Maßnahmen empfehlenswert, die im Artikel Ubuntu Server 14.04 – Webserver Apache absichern beschrieben werden.

Voraussetzungen

Wir haben unseren Server z. B. gemäß der Anleitung zur Basisinstallation für Ubuntu Server 14.04 installiert und durch Updates / Upgrades auf den neuesten Stand gebracht. Der Server hat die IPv4-Adresse 192.168.0.1 und den Rechnernamen server. Er gehört zur Domain test.local und ist auch unter www.test.local erreichbar. Die Befehle führen wir mit Root-Rechten aus, z. B. nach Öffnen einer Rootshell mit sudo -i.

LAMP-Server installieren

Zur Installation des LAMP-Servers benötigen wir nur einen Befehl:

root@server:~# apt install lamp-server^

Wichtig ist die Angabe des Caret-Zeichens ^ am Ende. Die Anweisung apt install wirkt damit im Wesentlichen wie ein Aufruf von tasksel install. Es wird nicht nur ein Programmpaket eingerichtet, sondern alle Pakete, die für eine bestimmte Serveraufgabe, hier LAMP, erforderlich sind.
Während des Installationsvorgangs wird ein Passwort für den MySQL-Benutzer root abgefragt, das wir unbedingt setzen sollten, da das root-Konto über erweiterte Rechte verfügt und man damit das Datenbanksystem komplett administrieren kann.
Alternativ können wir die Programmpakete auch einzeln installieren:

root@server:~# apt install apache2 libapache2-mod-php5 mysql-server php5-mysql

Webserver Apache konfigurieren

Für die Dauer der Konfigurationsarbeiten ist es empfehlenswert, den Apache-Server zu deaktivieren. Hierzu stoppen wir den entsprechenden Dienst apache2:

root@server:~# service apache2 stop

Standard-Website ersetzen

Apache kann mit Hilfe sogenannter „virtueller Hosts“ mehrere Websites verwalten, deren Dateien in Unterordnern des Verzeichnisses /var/www abgelegt werden können. Bei der Installation wird im Ordner /var/www/html eine Standard-Website mit der Datei index.html eingerichtet. Diese Datei ersetzen wir zunächst durch die Ordner und Dateien unseres Webauftritts. Dann beschränken wir mit den folgenden Befehlen den Vollzugriff auf den Benutzer root und erlauben zusätzlich Mitgliedern der Gruppe www-data, mit deren Rechten der Apache-Serverdienst läuft, lesenden Zugriff:

root@server:~# chown -R root:www-data /var/www/html
root@server:~# find /var/www/html \( -type d -exec chmod 0750 {} + \) -o \( -type f -exec chmod 0640 {} + \)

Restriktives Sicherheitsmodell umsetzen

Das per Apache-Voreinstellung realisierte Sicherheitsmodell erlaubt externe Zugriffe auf die Verzeichnisse /usr/share und /var/www und definiert zusätzliche dort zulässige Aktionen. Bei unserem restriktiven Ansatz werden wir den Verzeichniszugriff zunächst komplett sperren und nur bei Bedarf Ordner für Websites explizit freigeben. Hierzu führen wir die nachfolgenden Änderungen in der Hauptkonfigurationsdatei /etc/apache2/apache2.conf durch:

...
<Directory />
        #Options FollowSymLinks
        Options None
        AllowOverride None
        Require all denied
</Directory>
#<Directory /usr/share>
#       AllowOverride None
#       Require all granted
#</Directory>
#<Directory /var/www/>
#       Options Indexes FollowSymLinks
#       AllowOverride None
#       Require all granted
#</Directory>
...

Die Einstellungen für einzelne Verzeichnisse werden bei Apache innerhalb von Directory-Blöcken definiert. Im ersten Directory-Block werden durch die Anweisung Require all denied alle externen Zugriffe auf das Wurzelverzeichnis / inklusive aller Unterordner gesperrt. Diese Sperre wird mit Require all granted für die Verzeichnisse /usr/share und /var/www nachfolgend wieder aufgehoben. Um dies zu verhindern, kommentieren wir die beiden zugehörigen Blöcke mit # komplett aus.
In der Options-Anweisung legt man fest, welche zusätzlichen Aktionen in einem Verzeichnis erlaubt sind. Der Parameter FollowSymLinks innerhalb der Wurzelverzeichniskonfiguration ermöglicht Apache, symbolischen Links zu folgen. Da durch diese und andere Optionen in bestimmten Szenarien Sicherheitslücken entstehen können, deaktivieren wir sie mit Options None komplett.
Neben der Definition in Apache-Konfigurationsdateien können Verzeichniseinstellungen auch in .htaccess-Dateien im jeweiligen Website-Ordner gespeichert werden. Dies ist beispielsweise sinnvoll, falls der Website-Betreiber bei einem angemieteten Server keinen direkten Zugriff auf die Webserverkonfiguration hat. In der Apache-Anleitung zu .htaccess wird empfohlen, .htaccess-Dateien aus Performance- und Sicherheitsgründen möglichst nicht zu verwenden. Dieses Verhalten wird bereits während der Installation mit der Anweisung AllowOverride None als Standard festgelegt.

Apache-Einstellungen für Standard-Website ändern

Die Eigenschaften der Standard-Website von Apache werden in der Datei /etc/apache2/sites-available/000-default.conf definiert. Deren Inhalt ersetzen wir durch folgende Anweisungen:

<VirtualHost *:80>
  CustomLog ${APACHE_LOG_DIR}/access-html.log vhost_combined
  DocumentRoot /var/www/html
  ErrorLog ${APACHE_LOG_DIR}/error-html.log
  ServerAdmin webmaster@test.local
  ServerName www.test.local
  <Directory /var/www/html>
    Require all granted
  </Directory>
</VirtualHost>

Die spezifischen Angaben zu einzelnen Websites sind in VirtualHost-Blöcken zusammengefasst. In unserem Beispiel wertet Apache die Konfigurationseinträge zwischen <VirtualHost *:80> und </VirtualHost> aus, sobald eine Anfrage an den unter ServerName angegebenen virtuellen Host www.test.local an Port 80 gestellt wird. Port 80 wird standardmäßig vom „HyperText Transfer Protocol“ HTTP zur Übermittlung von Webseiten genutzt. Statt des Platzhalters * kann man auch eine dedizierte IP-Adresse angeben, an der der Server auf Anfragen reagieren soll.
In CustomLog und ErrorLog legen wir die Logdateien für Zugriffe und Fehler / Warnungen fest. Falls Zugriffe vom Standardformat abweichend gespeichert werden sollen, kann man die CustomLog-Anweisung mit einem der in apache2.conf vordefinierten Protokollformate ergänzen, in unserem Beispiel vhost_combined. Die Logdateinamen wählen wir abweichend von den voreingestellten Namen other_vhosts_access.log und error.log, um bei mehreren Websites eine getrennte Auswertung zu ermöglichen.
In ServerAdmin ist die E-Mail-Adresse hinterlegt, die Apache als Kontaktmöglichkeit in Fehlermeldungen an einen Client, in der Regel also einen Webbrowser, einfügt.
Der Eintrag DocumentRoot beinhaltet den Standardordner für Website-Inhalte /var/www/html, für den ein zusätzlicher Directory-Block mit verzeichnisbezogenem Inhalt definiert ist. Die Anweisung Require all granted bewirkt, dass alle Zugriffe auf Dateien innerhalb dieses Verzeichnisses inklusive eventueller Unterordner zulässig sind.
Weitere Details zu den Konfigurationsparametern finden sich in den Abschnitten Apache-Kernfunktionen und Log Files der Apache-Dokumentation.

Optional zusätzlichen Standardhost definieren

Falls der Apache-Server eine Anfrage erhält, die zu keinem virtuellen Host passt, werden die Einstellungen des ersten in der Konfiguration aufgeführten Hosts verwendet, der den Standardhost darstellt. Ruft ein Client also die Website server.test.local oder direkt die IP-Adresse 192.168.0.1 auf, nutzt Apache automatisch die Daten für www.test.local. Möchte man stattdessen unpassende Zugriffe mit einer Fehlermeldung im Webbrowser quittieren, bietet sich die Anlage eines speziellen Standardhosts vor allen anderen VirtualHost-Definitionen in 000-default.conf an:

<VirtualHost *:80>
  CustomLog ${APACHE_LOG_DIR}/access.log vhost_combined
  DocumentRoot /var/www
  ErrorLog ${APACHE_LOG_DIR}/error.log
  ServerName dummy
</VirtualHost>
...

Als DocumentRoot geben wir das vor externen Anfragen geschützte Verzeichnis /var/www an. Der Eintrag bei ServerName ist nicht von Bedeutung, sollte aber selbstverständlich nicht mit einem der anderen virtuellen Hosts übereinstimmen.
Die Definition unseres Standardhosts richtet sich nach der Apache-Dokumentation zur Unterstützung namensbasierter virtueller Hosts:

Innerhalb jedes <VirtualHost>-Blocks benötigen Sie zumindestens eine ServerName-Anweisung, um zu bestimmen, welcher Host bedient wird, und eine DocumentRoot-Anweisung, um anzugeben, wo im Dateisystem der Inhalt des Hosts abgelegt ist.

Es scheint aber möglich zu sein, einen Standard-VirtualHost-Block auch ohne DocumentRoot und ServerName anzulegen. Zumindest lief unser Testsystem selbst mit einer komplett leeren VirtualHost-Definition noch einwandfrei.

Apache-Zugriff per HTTPS aktivieren

Das Protokoll HTTP hat den Nachteil, dass Webseiten im Klartext übertragen werden. Um sensible Daten wie Passwörter besser zu schützen, eignet sich die Weiterentwicklung „HyperText Transfer Protocol Secure“ HTTPS. HTTPS sichert die Inhalte durch Verschlüsselung mit SSL („Secure Sockets Layer“) bzw. TLS („Transport Layer Security“) und gewährleistet zudem deren Integrität. Voraussetzung für die Nutzung von SSL / TLS ist ein von einer Zertifizierungsstelle ausgestelltes digitales (Server-)Zertifikat. Dieses beinhaltet eine Datei mit dem eigentlichen Zertifikat und eine Datei mit dem zugehörigen privaten Schlüssel.
Damit Apache Anfragen per HTTPS bearbeiten kann, müssen wir vorab mit a2enmod das Modul mod_ssl aktivieren:

root@server:~# a2enmod ssl

Die HTTPS-Einstellungen der Apache-Standard-Website sind in der Datei /etc/apache2/sites-available/default-ssl.conf gespeichert, die wir in Anlehnung an die HTTP-Konfigurationsdatei 000-default.conf ohne optionalen Standardhost aufbauen:

<VirtualHost *:443>
  CustomLog ${APACHE_LOG_DIR}/access-html-ssl.log vhost_combined
  DocumentRoot /var/www/html
  ErrorLog ${APACHE_LOG_DIR}/error-html-ssl.log
  ServerAdmin webmaster@test.local
  ServerName www.test.local
  SSLCertificateFile /etc/ssl/certs/www.test.local.pem
  SSLCertificateKeyFile /etc/ssl/private/www.test.local.key
  SSLEngine on
  <Directory /var/www/html>
    Require all granted
  </Directory>
</VirtualHost>

In der VirtualHost-Anweisung tragen wir statt 80 den Standardport für HTTPS-Verbindungen 443 ein. Mit SSLEngine on wird die Verarbeitung von HTTPS-Anfragen aktiviert. In SSLCertificateFile wird der Speicherort des Serverzertifikats angegeben. SSLCertificateKeyFile enthält den Dateinamen des zugehörigen privaten Schlüssels.
Die Dateien www.test.local.pem und www.test.local.key haben wir in unserem Beispiel nach der Anleitung Ubuntu Server 14.04 – Certificate Authority mit OpenSSL einrichten erzeugt. Sie eignen sich allerdings nur für den Einsatz in einem lokalen Netzwerk. Bei einem öffentlich zugänglichen Webserver benötigen wir ein Zertifikat einer anerkannten Zertifizierungsstelle, das von gängigen Webbrowsern ohne zusätzliche Maßnahmen als vertrauenswürdig eingestuft wird. Solch ein Zertifikat kann man entweder von kommerziellen Anbietern oder kostenlos von alternativen SSL-Zertifizierungstellen wie Let’s Encrypt beziehen. Die Zertifikatserstellung mit Let’s Encrypt ist Gegenstand des Beitrags Ubuntu Server 14.04 – SSL-Zertifikate mit Let’s Encrypt.
Im Verzeichnis /etc/apache2/sites-available können Konfigurationsdateien für eine oder mehrere Websites und verschiedene Protokolle abgespeichert und bei Bedarf mit dem Befehl a2ensite aktiviert werden. Hiermit wird im Verzeichnis /etc/apache2/sites-enabled ein symbolischer Link auf die Konfigurationsdatei erzeugt. Die HTTP-Standardkonfiguration 000-default.conf ist bereits nach der Installation wirksam. Die HTTPS-Standardkonfiguration default-ssl.conf aktivieren wir mit:

root@server:~# a2ensite default-ssl.conf

Ausführliche Informationen über die SSL- bzw. TLS-Unterstützung von Apache sind in der Dokumentation über mod_ssl nachzulesen.

Automatische Umleitung von HTTP auf HTTPS einrichten

Bei Webbrowsern wird die Eingabe einer Webadresse ohne Angabe des Protokolls üblicherweise automatisch durch das Voranstellen von http:// zu einem „Uniform Resource Locator“ URL ergänzt. Dieser Komfort geht allerdings auf Kosten der Sicherheit, da nicht das verschlüsselnde Protokoll HTTPS genutzt wird. Man kann das Problem serverseitig mit Hilfe des Apache-Moduls mod_rewrite lösen, das eine Umleitung von URLs ermöglicht.
Die Aktivierung von mod_rewrite erfolgt wie beim SSL-Modul mittels a2enmod:

root@server:~# a2enmod rewrite

Zur URL-Umleitung ergänzen wir die Datei /etc/apache2/sites-available/000-default.conf mit folgenden Anweisungen innerhalb des VirtualHost-Blocks für /var/www/html:

<VirtualHost *:80>
  ...
  RewriteEngine on
  RewriteCond %{HTTPS} off
  RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
  ...
</VirtualHost>

Mit RewriteEngine on wird mod_rewrite aktiviert. RewriteCond bestimmt, unter welchen Bedingungen eine Umleitung stattfindet. In unserem Fall wird mit %{…} die Servervariable HTTPS abgefragt, die den Wert off hat, falls die Verbindung nicht mit SSL / TLS aufgebaut wurde. Ist diese Bedingung erfüllt, werden mit RewriteRule alle Dateien (.*) auf den URL https://%{HTTP_HOST}%{REQUEST_URI} umgeleitet. Die Servervariable HTTP_HOST beinhaltet hierbei den Servernamen, beispielsweise www.test.local. Im Platzhalter REQUEST_URI ist der Dateiname mit vollständiger Pfadangabe, z. B. /sub/index.html, abgelegt.
Die Anweisung RewriteRule enthält am Ende zusätzliche durch Kommas getrennte Parameter, die sogenannten „RewriteRule Flags“, in eckigen Klammern. Der „Redirect“-Parameter R meldet dem aufrufenden Programm mit dem Statuscode 301, dass die Webseite dauerhaft unter der neuen Adresse zu erreichen ist, auf die die Umleitung verweist. Suchmaschinen können so beim Crawlen die neue statt der ursprünglichen Adresse hinterlegen. Der „Last“-Parameter L sorgt dafür, dass eventuell vorhandene weitere RewriteRule-Befehle nicht abgearbeitet werden. Er wäre also streng genommen in unserer einfachen Konstellation nicht erforderlich.
Alternativ zur Lösung mit mod_rewrite kann man auch die Redirect-Anweisung aus dem vorinstallierten Apache-Modul mod_alias verwenden. Im VirtualHost-Block für /var/www/html wäre dazu folgende Zeile einzufügen:

<VirtualHost *:80>
  ...
  Redirect permanent / https://www.test.local/
  ...
</VirtualHost>

„Security by Obscurity“-Maßnahmen

Die nachfolgenden „Security by Obscurity“-Maßnahmen bewirken keine erhöhte Sicherheit im eigentlichen Sinne, können aber potentiellen Angreifern die Arbeit durch das Vorenthalten von Informationen ein wenig erschweren.
Um die Ausgabe von Apache auf das Nötigste zu beschränken, führen wir in der Datei /etc/apache2/conf-available/security.conf folgende Änderungen durch:

...
#ServerTokens OS
ServerTokens Prod
...
#ServerSignature On
ServerSignature Off
...

Die Option ServerTokens bestimmt den Umfang der im Nachrichtenkopf („Header“) der Antwort auf eine HTTP-GET-Anfrage eines Clients enthaltenen Serverinformationen. Mit der Voreinstellung ServerTokens OS überträgt Apache im Eintrag Server des Antwort-Headers den Namen des Webservers, dessen Versionsnummer und das Betriebssystem, beim Testsystem Apache/2.4.7 (Ubuntu). Nach der Änderung auf Prod reduziert sich die Ausgabe auf den Namen Apache. Bei der Einstellung ServerTokens Full würde der Server-Eintrag zusätzlich noch Informationen über die verwendete PHP-Version preisgeben.
Die Option ServerSignature beinflusst die Ausgabe von Serverinformationen an Client-Programme wie z. B. Fehlermeldungen im Webbrowser. Durch das Ausschalten mit dem Wert Off wird eine Signatur wie Apache/2.4.7 (Ubuntu) Server at server.test.local Port 80 vollständig unterdrückt.
In der Regel wird bei Eingabe eines Verzeichnisnamens im Webbrowser die Startseite des Verzeichnisses wie index.html oder index.php ausgegeben. Zulässige Namen für Startseiten sind in der Datei /etc/apache2/mods-available/dir.conf unter dem DirectoryIndex-Eintrag gespeichert. Falls keine der dort aufgeführten Dateien existiert, liefert Apache in der Ausgangskonfiguration für die Ordner innerhalb des Website-Verzeichnisses /var/www/ einen automatisch generierten Index mit dem Verzeichnisinhalt zurück. Diese Daten können wichtige Anhaltspunkte für einen Angriff auf unseren Server geben und sollten daher nicht öffentlich gemacht werden.
Die Ausgabe des Verzeichnisindex wird durch den Parameter Indexes im Konfigurationseintrag Options gesteuert. Durch die Angabe von Options None in der Hauptkonfigurationsdatei /etc/apache2/apache2.conf haben wir die automatische Anzeige des Verzeichnisinhalts bereits abgeschaltet und es sind keine weiteren Schritte erforderlich.

Website freigeben

Bevor wir unsere Website für den öffentlichen Betrieb freigeben, testen wir noch die Syntax der Konfigurationsdateien mit dem „Apache HTTP server control interface“ apache2ctl:

root@server:~# apache2ctl configtest

Sollte das Ergebnis nicht Syntax OK lauten, korrigieren wir den / die Fehler anhand der angezeigten Fehlermeldung(en) und lassen den Test erneut durchlaufen. Nach erfolgreicher Syntaxprüfung können wir dann den anfangs gestoppten Apache-Dienst starten:

root@server:~# service apache2 start

Falls Apache bereits läuft, führen wir stattdessen einen Neustart durch:

root@server:~# service apache2 restart

MySQL-Server anpassen

Sicherheit erhöhen

Zum Installationsumfang von MySQL gehört das Sicherheitsskript mysql_secure_installation, mit dem man den Datenbankserver durch Ändern verschiedener Einstellungen zusätzlich absichern kann. Die Möglichkeiten sind im Einzelnen:

  1. Ein Passwort für den Benutzer root setzen.
  2. Anonyme Benutzerkonten entfernen.
  3. Den Zugriff auf das root-Konto aus dem Netzwerk unterbinden und auf localhost begrenzen.
  4. Testdatenbanken löchen.

Bei der für diesen Artikel genutzten MySQL-Version 5.5.46 waren die Maßnahmen 1, 2 und 4 nicht nötig, da während der LAMP-Installation das root-Passwort bereits gesetzt wurde und weder anonyme Benutzerkonten noch Testdatenbanken angelegt wurden. Die Zugriffsbeschränkung für den Benutzer root unter Punkt 3 realisieren wir wie folgt:

root@server:~# mysql -p -u root
Enter password: ***
...
mysql> DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
mysql> exit

Bei anderen MySQL-Versionen können zusätzlich die Maßnahmen 2 und / oder 4 erforderlich sein. Die entsprechenden Befehle in der MySQL-Kommandozeile lauten:

...
mysql> DELETE FROM mysql.user WHERE User='';
mysql> DROP DATABASE test;
mysql> DELETE FROM mysql.db WHERE Db='test'
...

Möchte man die Änderungen lieber interaktiv durchführen, kann man natürlich auch das Sicherheitsskript ausführen:

root@server:~# mysql_secure_installation

Warnungen und Fehler beseitigen

Treten beim Start / Neustart des MySQL-Servers Probleme auf, so werden diese als Warn- und Fehlermeldungen in Logdateien protokolliert. In der Ausgangskonfiguration von MySQL finden sich hier zwei – allerdings unkritische – Warnungen.
In der Logdatei /var/log/mysql/error.log ist die nachfolgende Meldung aufgeführt:

...
... [Warning] Using unique option prefix myisam-recover instead of myisam-recover-options is deprecated and will be removed in a future release. Please use the full name instead.
...

Die Logdatei /var/log/upstart/mysql.log enthält den Eintrag:

...
... [Warning] Using unique option prefix key_buffer instead of key_buffer_size is deprecated and will be removed in a future release. Please use the full name instead.
...

Um die beiden Warnmeldungen abzustellen, ändern wir die entsprechenden Optionsnamen in der MySQL-Konfigurationsdatei /etc/mysql/my.cnf:

...
#key_buffer             = 16M
key_buffer_size         = 16M
...
#myisam-recover         = BACKUP
myisam-recover-options  = BACKUP
...

Abhängig von der Serverkonfiguration kann in /var/log/upstart/mysql.log folgender Fehler auftauchen, der den Serverbetrieb aber ebenso wenig beeinträchtigt wie die beiden vorangehend genannten Warnungen:

...
^G/usr/bin/mysqladmin: connect to server at 'localhost' failed
error: 'Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (...)'
Check that mysqld is running and that the socket: '/var/run/mysqld/mysqld.sock' exists!
...

Ursache ist das in der Upstart-Konfigurationsdatei /etc/init/mysql.conf definierte Skript post-start. Dieses Skript wird aufgerufen, nachdem der MySQL-Serverprozess /usr/sbin/mysqld in den Status spawned übergegangen ist und prüft, ob der Datenbankserver erreichbar ist. Nach erfolgreichem Abarbeiten des Skripts wechselt der Prozess in den Status started und ist einsatzbereit.
Das Problem tritt auf, wenn der MySQL-Server sich beim Aufruf von post-start noch nicht vollständig im Status spawned befindet und daher mit mysqladmin ping nicht ansprechbar ist. Abhilfe schafft das Einfügen einer Wartezeit am Anfang von post-start mit dem Befehl sleep. Bei dem für diesen Beitrag eingesetzten Server erwies sich ein Wert von 3 Sekunden als ausreichend:

...
post-start script
   sleep 3
...

Damit die Änderungen wirksam werden, starten wir den MySQL-Server neu:

root@server:~# service mysql restart