Apache - virtuelle Hosts

  • Datum: 2024-06-13

  • Distribution: getestet mit Debian 12 (ebenso mit Debian 11 und 10 machbar)

Mit virtuellen Hosts lassen sich mehrere Kunden mittels nur einer Apache-Installation bedienen. Man unterscheidet:

  1. IP-basierte virtuelle Hosts (die Netzwerkkarte erhält mehrere Adressen, früher: IPv4-Aliase)

  2. Namensbasierte virtuelle Hosts (der Host hat nur eine Adresse, wird aber über mehrere Namen angesprochen)

Die Webdokumente werden dann aus verschiedenen Verzeichnissen ausgeliefert, in unserem Fall seien dies

/srv/vhosts/firma1
/srv/vhosts/firma2

Um die Konfigurationsdateien anschaulich ohne Kommentare und Leerzeilen ausgeben zu können, ist der folgende Alias sinnvoll:

alias gg="grep --color -Ev '^\s*#|^$'"
alias gg >> ~/.bashrc

IP-basierte virtuelle Hosts

Das Folgende soll nur als Übersicht dienen. Im einfachsten Fall werden einer Netzwerkkarte mittels Aliasen mehrere Adressen mitgegeben:

ifconfig eth0:0 80.0.0.1 netmask 255.255.255.224  # für Firma 1
ifconfig eth0:1 80.0.0.2 netmask 255.255.255.224  # für Firma 2

Die erste Konfigurationsdatei würde dann entsprechend so aussehen, man beachte <VirtualHost 80.0.0.1:80>, außerdem muss als wichtige Direktive ServerName aktiviert und angepasst werden:

<VirtualHost 80.0.0.1:80>
    ServerName www.firma1.test
    ServerAlias debvhost.firma1.test
    ServerAdmin webmaster@firma1.test
    DocumentRoot /srv/vhosts/firma1
    <Directory /srv/vhosts/firma1/>
       Options none
       AllowOverride none
       Require all granted
    </Directory>
    ErrorLog ${APACHE_LOG_DIR}/error-firma1.log
    CustomLog ${APACHE_LOG_DIR}/access-firma1.log combined
</VirtualHost>

Die zweite Konfigurationsdatei sieht dann in etwa so aus, man beachte wiederum die erste Zeile, nunmehr auf <VirtualHost 80.0.0.2:80> geändert, weiterhin spielt die Zeile ServerName www.firma2.test eine wichtige Rolle:

<VirtualHost 80.0.0.2:80>
    ServerName www.firma2.test
    ServerAlias debvhost.firma2.test
    ServerAdmin webmaster@firma2.test
    DocumentRoot /srv/vhosts/firma2
    <Directory /srv/vhosts/firma2/>
       Options none
       AllowOverride none
       Require all granted
    </Directory>
    ErrorLog ${APACHE_LOG_DIR}/error-firma2.log
    CustomLog ${APACHE_LOG_DIR}/access-firma2.log combined
</VirtualHost>

Schließlich muss die Namensauflösung z.B. mittels /etc/hosts mit zwei IP-Adressen durchgeführt werden:

80.0.0.1   www.firma1.test   debvhost.firma1.test
80.0.0.2   www.firma2.test   debvhost.firma2.test

Namensbasierte virtuelle Hosts

In älteren Apache-Versionen gab es dazu als Voraussetzung in der globalen httpd.conf die Anweisung NameVirtualHost *:80, die aber nur das Akzeptieren von bestimmten Requests betraf und nicht das Port-Listening definiert (das geschieht mittels Listen 80). Siehe dazu https://httpd.apache.org/docs/2.2/en/vhosts/name-based.html

Die grundlegenden Schritte zur praktischen Einrichtung sind:

  1. Verschiedene Verzeichnisse für DocumentRoot erzeugen, z.B. /srv/vhosts/firma1

  2. Dort drin unterschiedliche index.html Dateien erzeugen

  3. Kopieren und Umbenennen der Datei 000-default.conf, z.B. in firma1.conf

  4. Anpassungen in firma1.conf

  5. Kopieren und Umbenennen der firma1.conf in firma2.conf

  6. Aktivieren der VHosts, reload der Config

  7. Namenausflösung sicherstellen (anfänglich/testweise in der /etc/hosts)

  8. Testen…

zu 1) und 2) DocumentRoot, Rechte und Webdokumente

Zuerst legen wir die beiden Verzeichnisse an, setzen die Rechte so, dass Kunden selber Dateien ablegen können und erzeugen zwei Textdateien für das abschließende Testen.

mkdir -p /srv/vhosts/firma{1,2}
chown -v www-data: /srv/vhosts/firma?

echo 'Das ist der erste VHost für Firma 1' > /srv/vhosts/firma1/index.html
echo 'This is the VHost for Firma 2' > /srv/vhosts/firma2/index.html

zu 3) und 4) Konfigurationsdatei für Firma1 erstellen

cd /etc/apache2/sites-available
cp 000-default.conf firma1.conf
vi firma1.conf
gg firma1.conf
    <VirtualHost *:80>
            ServerName www.firma1.test
            ServerAlias debvhost.firma1.test
            ServerAdmin webmaster@firma1.test
            DocumentRoot /srv/vhosts/firma1
            <Directory /srv/vhosts/firma1/>
                Options none
                AllowOverride none
                Require all granted
            </Directory>
            ErrorLog ${APACHE_LOG_DIR}/error-firma1.log
            CustomLog ${APACHE_LOG_DIR}/access-firma1.log combined
    </VirtualHost>

zu 5) Konfigurationsdatei für Firma2 erstellen

gg firma1.conf | sed 's/firma1/firma2/g' > firma2.conf
cat firma2.conf
    <VirtualHost *:80>
            ServerName www.firma2.test
            ServerAlias debvhost.firma2.test
            ServerAdmin webmaster@firma2.test
            DocumentRoot /srv/vhosts/firma2
            <Directory /srv/vhosts/firma2/>
                Options none
                AllowOverride none
                Require all granted
            </Directory>
            ErrorLog ${APACHE_LOG_DIR}/error-firma2.log
            CustomLog ${APACHE_LOG_DIR}/access-firma2.log combined
    </VirtualHost>

zu 6) Aktivieren der VHosts, reload der Config

a2ensite firma1
a2ensite firma2
ls -ltrc /etc/apache2/sites-enabled/
apachectl graceful

zu 7) Namenausflösung sicherstellen (anfänglich/testweise in der /etc/hosts)

echo '127.0.0.1 debvhost.firma1.test debvhost.firma2.test' >> /etc/hosts
echo '127.0.0.1 www.firma1.test www.firma2.test firma1.test firma2.test' >> /etc/hosts

zu 8) Testen der Vhost-Konfiguration

# Es erscheint "Das ist der erste VHost für Firma 1":
wget -qO- debvhost.firma1.test
wget -qO- www.firma1.test

# Es erscheint "This is the VHost for Firma 2":
wget -qO- debvhost.firma2.test
wget -qO- www.firma2.test

Privates Verzeichnis für Firma 2

Für Firma 2 soll nun ein privates Verzeichnis mit Zugriffsschutz eingerichtet werden, hier direkt in der Konfigurationsdatei (also nicht mittels separater .htaccess-Datei, das ist unter Zur Methode A) via .htaccess beschrieben).

Zuerst legen wir das Verzeichnis und ein kleines Webdokument an:

mkdir /srv/vhosts/firma2/private
echo "Willkommen im privaten Verzeichnis." > /srv/vhosts/firma2/private/index.html

Nun folgt das Ergänzen der Konfigurationsdatei /etc/apache2/sites-enabled/firma2.conf um 6 Zeilen, beginnend mit der einleitenden Zeile, die das neue Verzeichnis definiert: <Directory "/srv/vhosts/firma2/private/">. Ohne Kommentare sieht diese Konfigurationsdatei schließlich so aus:

<VirtualHost *:80>
        ServerName www.firma2.test
        ServerAlias debvhost.firma2.test
        ServerAdmin webmaster@firma2.test
        DocumentRoot /srv/vhosts/firma2
        <Directory /srv/vhosts/firma2/>
            Options none
            AllowOverride none
            Require all granted
        </Directory>

        <Directory "/srv/vhosts/firma2/private/">
            AuthName "Private"
            AuthType Basic
            AuthUserFile /etc/apache2/passwords/access-passwd.firma2
            Require valid-user
        </Directory>

        ErrorLog ${APACHE_LOG_DIR}/error-firma2.log
        CustomLog ${APACHE_LOG_DIR}/access-firma2.log combined
</VirtualHost>

Nicht vergessen, diese Änderung mit apachectl graceful einzulesen!

Jetzt wird noch eine entsprechende Password-Datei mit zwei Benutzern erzeugt (bei der zweiten Zeile -c weglassen!!):

htpasswd -c /etc/apache2/passwords/access-passwd.firma2 user01
htpasswd /etc/apache2/passwords/access-passwd.firma2 user02

Und schließlich kann es schon ans Testen gehen; ohne die Benutzerauthentifizierung erhalten wir aber keinen Output (wget-Fehler via Exit-Code 6):

wget -qO- www.firma2.test/private/ ; echo $?

Übergeben wir nun aber den Benutzernamen samt Passwort, wird „Willkommen im privaten Verzeichnis.“ mit dem nachfolgenden Exit-Code 0 (Erfolg) ausgegeben:

wget -qO- --user="user01" --password="1234567" www.firma2.test/private/ ; echo $?

DNS-Zonen für die virtuellen Hosts in Apache

Anstelle der einfachen, dezentralen Namensauflösung mittels ‚/etc/hosts‘, ist für jeden Kunden eine eigene Zone empfehlenswert.

Dazu hier zwei Beispiele für unsere VHosts mittels bind9. Diese Zonendefinitionen werden einfach zur bestehenden Datei ‚/etc/bind/named.conf.local‘ hinzugefügt.

zone "firma1.test" {
        type master;
        file "/var/lib/bind/db.firma1.test";
};

zone "firma2.test" {
        type master;
        file "/var/lib/bind/db.firma2.test";
};

Und dazu ein erstes passendes Zonenfile als Beispiel (db.firma1.test):

$TTL    604800
@       IN      SOA     dns1.firma1.test. root.dns1.firma1.test. (
                            5         ; Serial
                        604800         ; Refresh
                        86400         ; Retry
                        2419200         ; Expire
                        604800 )       ; Negative Cache TTL
;
@       IN      NS      dns1.firma1.test.

; A-Records sind auf alle Fäller erforderlich, ein CNAME-Record wird
; mittels dem tatsächlichen Hostnamen realisiert:
dns1     IN      A       10.0.2.200
dns1     IN      A       192.168.2.222

; Für ganz kurzen Aufruf: nslookup firma1.test
@       IN      A       10.0.2.200
@       IN      A       192.168.2.222
www     IN      CNAME   dns1

Im zweiten Zonenfile (db.firma2.test) ist der Hostname dns1 wiederum derselbe, nur der Domänennname ändert sich:

$TTL    604800
@       IN      SOA     dns1.firma2.test. root.dns1.firma2.test. (
                            5         ; Serial
                        604800         ; Refresh
                        86400         ; Retry
                        2419200         ; Expire
                        604800 )       ; Negative Cache TTL
;
@       IN      NS      dns1.firma2.test.

; A-Records sind auf alle Fäller erforderlich, ein CNAME-Record wird
; mittels dem tatsächlichen Hostnamen realisiert:
dns1     IN      A       10.0.2.200
dns1     IN      A       192.168.2.222

; Für ganz kurzen Aufruf: nslookup firma2.test
@       IN      A       10.0.2.200
@       IN      A       192.168.2.222
www     IN      CNAME   dns1

SSL-Verschlüsselung für die virtuellen Hosts

Skizzierung der wichtigsten Schritte für Apache 2.2.22

$ cd /etc/ssl/
$ openssl req -new -outform PEM -out certs/testing.pem -newkey rsa:2048 -nodes \
    -keyout private/testing.key -keyform PEM -days 3650 -x509

$ openssl x509 -fingerprint -noout -in certs/testing.pem
$ a2enmod ssl

Konfiguration für VHost 1

$ cd /etc/apache2/sites-available/
$ cp default-ssl test01.test-ssl
$ vi test01.test-ssl
Anpassen:   <VirtualHost www.test01.test:443>
Anpassen:   DocumentRoot, Directory- und Logfile-Einstellungen (s.o.)
Anpassen:   Namen der Zertifikatsdateien (.pem und .key der Direktiven
              SSLCertificateFile bzw. SSLCertificateKeyFile)
$ a2ensite test01.test-ssl

Konfiguration für VHost 2

$ cp test01.test-ssl test02.test-ssl
$ vi test02.test_ssl
Anpassen:   <VirtualHost www.test02.test:443>
Anpassen:   DocumentRoot, Directory- und Logfile-Einstellungen (s.o.)
Anpassen:   Namen der Zertifikatsdateien (.pem und .key der Direktiven
              SSLCertificateFile bzw. SSLCertificateKeyFile)
$ a2ensite test02.test-ssl
$ /etc/init.d/apache2 restart

Tests im Browser

https://192.168.0.222/   <--- kein Zugang (Versuch, die Website zu identifizieren ist fehlgeschlagen)!
https://www.test01.test/ <-- OK
https://www.test02.test/ <-- OK

Aktivieren von SSL für mehrere virtuelle Hosts (Server Name Indication)

  1. Änderung in der Datei /etc/apache2/ports.conf

Im Abschnitt <IfModule…> oberhalb der Zeile Listen 443 hinzufügen von (bei Apache 2.4 gibt es „NameVirtualHost“ nicht mehr):

NameVirtualHost *:443
  1. Anpassung der betreffenden Vhost-Konfiguration für SSL

Änderung von <VirtualHost *default:443>* auf

<VirtualHost *:443>

Erzwingen von https beim Zugriff auf ein Unterverzeichnis

Voraussetzung: a2enmod rewrite

In der Vhost-Konfiguration FÜR PORT 80 unterhalb von DocumentRoot /var/www/test02.test hinzuzufügen:

# Nur für das Unterverzeichnis 'mail' die Verschlüsselung erzwingen
RewriteEngine on
ReWriteCond %{SERVER_PORT} !^443$
RewriteRule ^/mail/(.*) https://%{HTTP_HOST}/mail/$1 [NC,R,L]

Zur Bedeutung der Flags:

nocase|NC

Beim Suchmuster wird nicht zwischen Groß- und Kleinschreibung unterschieden

redirect|R[=Statuscode]

Normalerweise sorgt RewriteRule für eine interne URI-Änderung

last|L

Mit diesem Flag können Sie dafür sorgen, dass das Rewriting sofort beendet wird; weitere RewriteRules werden nicht mehr beachtet.

Siehe auch: