Multiple Websites and Subdomains with SSL/TLS in Apache2: Virtualhosts

Powered by Drupal
Submitted by Sam Hobbs on

Want to host more than one website on your Raspberry Pi, without having to pay for multiple IP addresses? You can do this easily using Apache’s name-based VirtualHost configuration feature. This feature allows someone to connect to your Raspberry Pi (or other server) and get served different content based on the host header they sent with their request. This is automatic, and the user is none the wiser: they simply type your web address in the header, and your server uses that information to decide which website to display. Unless you tell them, they won’t know the Pi is also hosting other content.

General Rules

Out of the box, Apache2 on Raspbian has two files with VirtualHost configuration parameters inside. One is at /etc/apache2/sites-available/default and the other is at /etc/apache2/sites-available/default-ssl. The default file is enabled (which symlinks it to /etc/apache2/sites-enabled) and the default-ssl is not. You can have as many or as few VHosts as you like; you can put all the VirtualHosts in the same file, or store them separately – Apache2 will work just the same either way. If you opt for the different files method, here are some useful commands. Use this one to enable a site you’ve added to /etc/apache2/sites-available/site:

sudo a2ensite site

And use this one to disable it again:

sudo a2dissite site

Apache reads configuration files stored in /etc/apache2/sites-enabled/ in alphanumerical order. The first VirtualHost block it reads is set as the default, and is used if no host header information is sent in the request. If more than one VirtualHost is defined, then Apache matches the host header against the ServerName and ServerAlias directives defined in the VirtualHosts to decide which content to serve. If no host header is sent, or a match cannot be found, then the default VHost is used. You can test which is the default VHost by typing the Pi’s IP address into your browser’s address bar. If you’re confused by this, don’t worry. It’ll probably make sense when you see some examples. I’m going to cover HTTP hosts first because they’re easiest, and then move on to SSL/TLS HTTPS hosts because they have some extra bits to consider.

HTTP Hosts

Here is a typical VirtualHost block:

<VirtualHost *:80>
        ServerAdmin webmaster@samhobbs.co.uk
        ServerName www.samhobbs.co.uk:80
        ServerAlias samhobbs.co.uk

        DocumentRoot /var/www
 
        <Directory /var/www/>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride all
                Order allow,deny
                allow from all
        </Directory>

        ErrorLog ${APACHE_LOG_DIR}/samhobbs/error.log
        LogLevel warn
        CustomLog ${APACHE_LOG_DIR}/samhobbs/access.log combined
</VirtualHost>
  1. The VirtualHost line tells Apache to match any IP address on port 80
  2. ServerAdmin defines the email address that is sent in error messages so that users can contact you about problems
  3. ServerName is the fully qualified domain name of the site. You can optionally include the port, e.g. www.example.com:80 for port 80
  4. ServerAlias provides additional names to match against host headers when Apache is deciding on which virtual host to use.
  5. DocumentRoot is the path to the directory that contains all the site data. In Debian, Apache’s data directory is /var/www. If you have lots of sites, then you probably want to make a directory for each site like /var/www/site1 /var/www/site2, and specify the appropriate directory here
  6. Directory specific options can be set inside a directory block. Options starts the list
    • Indexes means that if there is no index.html or index.php file inside the directory, Apache will create an auto formatted list of everything in the directory.
    • FollowSymLinks allows Apache to follow symbolic links
    • MultiViews allows content driven negotiation to take place, where the server can decide which version of a page (if more than one version exists) to send based on the client’s browser preferences
    • AllowOverride all means that the global defaults for Apache can be overridden with an .htaccess file placed inside the directory.
    • The “order allow,deny…” section allows you to specify IP ranges to block or enable. This one allows everyone.
  7. ErrorLog specifies where Apache should write the error log file for this VHost. ${APACHE_LOG_DIR} is /var/log/apache2. I like to create a new directory for each VHost like /var/log/apache2/samhobbs and then stick the log files in there.
  8. LogLevel specifies the severity of event you’d like to be written to the log.
  9. The CustomLog format combines information about access, agent and referrer into one file.

Adding More VHosts

#============================== ANTI PROXY SPAM =============================

<VirtualHost *:80>
        ServerName default.only
        <Location />
                Order allow,deny
                Deny from all
        </Location>

        ErrorLog ${APACHE_LOG_DIR}/spam/error.log
        LogLevel warn
        CustomLog ${APACHE_LOG_DIR}/spam/access.log combined

</VirtualHost>

#================================= WEBSITE ===================================

<VirtualHost *:80>
        ServerAdmin webmaster@samhobbs.co.uk
        ServerName www.samhobbs.co.uk:80
        ServerAlias samhobbs.co.uk

        DocumentRoot /var/www/samhobbs/
 
        <Directory /var/www/samhobbs/>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride all
                Order allow,deny
                allow from all
        </Directory>

        ErrorLog ${APACHE_LOG_DIR}/samhobbs/error.log
        LogLevel warn
        CustomLog ${APACHE_LOG_DIR}/samhobbs/access.log combined
</VirtualHost>

#============================= SECOND WEBSITE ===============================

<VirtualHost *:80>
        ServerAdmin webmaster@samhobbs.co.uk
        ServerName tomhobbs.co.uk:80
        ServerAlias www.tomhobbs.co.uk

        DocumentRoot /var/www/tomhobbs/
 
        <Directory /var/www/tomhobbs/>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride all
                Order allow,deny
                allow from all
        </Directory>

        ErrorLog ${APACHE_LOG_DIR}/tomhobbs/error.log
        LogLevel warn
        CustomLog ${APACHE_LOG_DIR}/tomhobbs/access.log combined
</VirtualHost>

In the above file, there are two “real” websites (www.samhobbs.co.uk and tomhobbs.co.uk) plus that default VHost that blocks everything else. Notice that:

  1. Each “real” website has its own directory (e.g. /var/www/site1 and /var/www/site2. If you place one website inside another’s directory then you’ll be able to access website 2 from within website 1, e.g. www.site1.com/site2/
  2. Each website’s logs are written to a separate location (/var/log/apache2/site1 and /var/log/apache2/site2) for ease of use. This is also handy if you want to use something like Webalizer for analytics.
  3. The webmaster email address is the same for both because in both cases I’m the admin

The default.only VHost is just there to block drive-by spam and automated script attacks that hammer IP addresses at random. My public IP address is 195.166.151.235 – try pasting this into your search bar to see how the default VHost will look to anyone trying the server by its IP. This blocks more spam than you might think, and takes a bit of load off your server: no legitimate user is going to type an IP address into their browser to visit your page, so your processing power and bandwidth are saved for legitimate users. Here’s an example of a post that was blocked by this technique. I don’t know exactly what this is, maybe comment spam, but you don’t have to be a genius to realise it wasn’t good news:

173.230.149.43 - - [07/Jan/2014:15:46:32 +0000] "POST //%63%67%69%2D%62%69%6E/%70%68%70?%2D%64+%61%6C%6C%6F%77%5F%75%72%6C%5F%69%6E%63%6C%75%64%65%3D%6F%6E+%2D%64+%73%61%66%65%5F%6D%6F%64%65%3D%6F%66%66+%2D%64+%73%75%68%6F%73%69%6E%2E%73%69%6D%75%6C%61%74%69%6F%6E%3D%6F%6E+%2D%64+%64%69%73%61%62%6C%65%5F%66%75%6E%63%74%69%6F%6E%73%3D%22%22+%2D%64+%6F%70%65%6E%5F%62%61%73%65%64%69%72%3D%6E%6F%6E%65+%2D%64+%61%75%74%6F%5F%70%72%65%70%65%6E%64%5F%66%69%6C%65%3D%70%68%70%3A%2F%2F%69%6E%70%75%74+%2D%64+%63%67%69%2E%66%6F%72%63%65%5F%72%65%64%69%72%65%63%74%3D%30+%2D%64+%63%67%69%2E%72%65%64%69%72%65%63%74%5F%73%74%61%74%75%73%5F%65%6E%76%3D%30+%2D%64+%61%75%74%6F%5F%70%72%65%70%65%6E%64%5F%66%69%6C%65%3D%70%68%70%3A%2F%2F%69%6E%70%75%74+%2D%6E HTTP/1.1" 403 465 "-" "-"

A note about VHost file locations

Installation instructions for some software like Squirrelmail (including my own tutorial) ask you to create symbolic links from that program’s configuration folder to Apache’s /etc/apache2/conf.d/ folder. Remember how I said that the first VirtualHost block that Apache reads is used as the default? If the symlinked configuration files contain VirtualHost blocks then they may be loaded before your default.only VirtualHost block, and become the default. So, you have two options: you can either symlink somewhere else, like /etc/apache2/sites-available/squirrelmail and then enable them (e.g. sudo a2ensite squirrelmail). This should allow you to control the order. Alternatively, you could add that default.only VirtualHost to the start of the squirrelmail config file, so that it is still read first.

HTTPS Hosts

Name based HTTPS hosts are a little more complicated that HTTP hosts. You can still get Apache to choose which content to serve based on the host header, but since the encrypted connection is established before this negotiation happens, every HTTPS VirtualHost must use the same SSL Certificate. The SSL cert that is defined in the first SSL/TLS VirtualHost block that is read is used for all HTTPS VirtualHosts on the server. This can lead to certificate errors in a web browser, since the Common Name on the certificate won’t match the domain name for the second site (assuming you use a cert that matches your main site as the default cert). For websites like my brother’s, that’s probably not an issue as you may not be serving any HTTPS content to normal users, i.e. only the admin backend uses SSL/TLS. Your communications will still be encrypted, but you’ll have to click through a warning since the identity of the site can’t be verified. If you don't have your own SSL certificate, you might like to generate one yourself and get it signed by CAcert Here’s an example:

<IfModule mod_ssl.c>
NameVirtualHost *:443

#=============================== ANTI SPAM ================================
<VirtualHost *:443>
        ServerName default.only
        <Location />
                Order allow,deny
                Deny from all
        </Location>

        SSLEngine on
        SSLCertificateFile /path/to/your/cert.crt
        SSLCertificateKeyFile /path/to/your/key.key
</VirtualHost>

#================================ WEBSITE ===================================

<VirtualHost *:443>
        ServerAdmin webmaster@samhobbs.co.uk
        ServerName www.samhobbs.co.uk
        ServerAlias samhobbs.co.uk

        DocumentRoot /var/www/samhobbs

        <Directory /var/www/samhobbs>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride all
                Order allow,deny
                allow from all
        </Directory>

        ErrorLog ${APACHE_LOG_DIR}/samhobbs/error.log
        LogLevel warn
        CustomLog ${APACHE_LOG_DIR}/samhobbs/ssl_access.log combined

        SSLEngine on
        SSLCertificateFile /path/to/your/cert.crt
        SSLCertificateKeyFile /path/to/your/key.key

        <FilesMatch "\.(cgi|shtml|phtml|php)$">
                SSLOptions +StdEnvVars
        </FilesMatch>
        <Directory /usr/lib/cgi-bin>
                SSLOptions +StdEnvVars
        </Directory>

        BrowserMatch "MSIE [2-6]" \
                nokeepalive ssl-unclean-shutdown \
                downgrade-1.0 force-response-1.0
        # MSIE 7 and newer should be able to use keepalive
        BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
</VirtualHost>

#============================= SECOND WEBSITE ================================

<VirtualHost *:443>
        ServerAdmin webmaster@samhobbs.co.uk
        ServerName tomhobbs.co.uk
        ServerAlias www.tomhobbs.co.uk

        DocumentRoot /var/www/tomhobbs

        <Directory /var/www/tomhobbs>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride all
                Order allow,deny
                allow from all
        </Directory>

        ErrorLog ${APACHE_LOG_DIR}/tomhobbs/error.log
        LogLevel warn
        CustomLog ${APACHE_LOG_DIR}/tomhobbs/ssl_access.log combined

        SSLEngine on
        SSLCertificateFile /path/to/your/cert.crt
        SSLCertificateKeyFile /path/to/your/key.key

        <FilesMatch "\.(cgi|shtml|phtml|php)$">
                SSLOptions +StdEnvVars
        </FilesMatch>
        <Directory /usr/lib/cgi-bin>
                SSLOptions +StdEnvVars
        </Directory>

        BrowserMatch "MSIE [2-6]" \
                nokeepalive ssl-unclean-shutdown \
                downgrade-1.0 force-response-1.0
        # MSIE 7 and newer should be able to use keepalive
        BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
</VirtualHost>

</IfModule>

Some additional things worth noting:

  1. NameVirtualHost *:443 is required before your first HTTPS VirtualHost, and tells Apache you want to use name based SSL/TLS VirtualHosts.
  2. You may see an IfModule statement bracketing your SSL hosts in /etc/apache2/sites-available/default-ssl – this is used to make sure if mod_ssl is missing, Apache can still start properly (everything inside the statement is ignored). To enable the SSL module, use sudo a2enmod ssl.

The same rules apply with HTTPS VirtualHosts as applied with HTTP VirtualHosts with regard to programs like Squirrelmail. You can check which is the default SSL VHost by typing your public WAN IP address into the address bar preceded with https:// . This will also show you which cert is in use (click the padlock icon location next to the address bar to see cert details); expect to get a certificate error regardless of which cert you’ve set as your IP won’t match the certificate’s Common Name!

Redirecting HTTP to HTTPS

You may find it useful to redirect all HTTP traffic to HTTPS for things like webmail. Here’s an example configuration for Squirrelmail. Define an HTTP virtualhost that just redirects traffic, and then add a HTTPS virtualhost as normal:

#=========================== HTTP redirect to HTTPS ==================================

<VirtualHost *:80>
ServerName webmail.samhobbs.co.uk
<IfModule mod_rewrite.c>
  <IfModule mod_ssl.c>
    <Location />
      RewriteEngine on
      RewriteCond %{HTTPS} !^on$ [NC]
      RewriteRule . https://%{HTTP_HOST}%{REQUEST_URI}  [L]
    </Location>
  </IfModule>
</IfModule>
</VirtualHost>

#================================ SQUIRRELMAIL =====================================

<IfModule mod_ssl.c>
<VirtualHost *:443>
  DocumentRoot /usr/share/squirrelmail
  ServerName webmail.samhobbs.co.uk

<Directory /usr/share/squirrelmail>
  Options FollowSymLinks
  <IfModule mod_php5.c>
    php_flag register_globals off
  </IfModule>
  <IfModule mod_dir.c>
    DirectoryIndex index.php
  </IfModule>

  # access to configtest is limited by default to prevent information leak
  <Files configtest.php>
    order deny,allow
    deny from all
    allow from 127.0.0.1
  </Files>
</Directory>

ErrorLog ${APACHE_LOG_DIR}/squirrelmail/error.log
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/squirrelmail/ssl_access.log combined

SSLEngine on
SSLCertificateFile /path/to/your/cert.crt
SSLCertificateKeyFile /path/to/your/key.key

</VirtualHost>
</IfModule>

Perfect Forward Secrecy, and HTTP Strict Transport Security (HSTS)

Perfect forward secrecy is a tool that helps mitigate the effects of an attacker obtaining your private key, and HTTP Strict Transport Security helps to prevent Man In The Middle (MITM) attacks. I have covered both of these things in this tutorial about configuring this site to be SSL-only. Hopefully, that has helped you get your head around SSL/TLS VirtualHosts in Apache2. If you’re stuck, let me know and I’ll try to help out!

Comments

Hi Sam,
Thanks. This get rid of the warnings except "updating apache." Maybe need to wait the new version to update. I want to know how to get known these prompts and your directive. It is so precisely.

Jeff

I have followed your tutorial to set up squirrlemail and in doing so now if i type in my local ip address of a different site hosted on the same server it tries to resolve this to HTTPS. what section do i need to change to undo this?

Simon, What exactly do you mean "type in the local IP address of a different site hosted on the same server"? If you mean you have been accessing sites using 1.2.3.4/foo and 1.2.3.4/bar for "sites" foo and bar, then you might want to split the content on your "sites" up into several virtualhosts (at the moment, it's two different sections of the same site). If you just want to turn HTTPS off, and you used the code under "If you want an HTTPS-only solution, you can replace the virtualhost for port 80 with this", then you should uncomment that code, since you don't want an HTTPS-only solution ;) Your server only has one local IP address, and Apache decides which content to serve by matching the hostname sent in the HTTP request to the ServerName and ServerAlias in your virtualhost(s) in the apache configuration files. If you want to get another site (yourdomain2.com instead of the default virtualhost yourdomain.com) you need to send a different hostname in the request (i.e. don't type an IP address into your browser, use a hostname). One simple way to change the hostname if you don't have "proper" global DNS resolution is to change the hosts file on your computer to map the hostname you're using in the virtualhost to the local IP address. At the moment, if all your content is in one virtualhost, then that block of code is rewriting everything to https. If you split it up, you can redirect some to https without affecting the rest. Sam

Hi Sam,

thanks for the quick reply.

Thats exactly it. got sites foo and bar and 5 virtual host files to redirect to each from my subdomains. I will reread that section and make the changes, i thought it meant if you want the squirrelmail to only use HTTPS :(.

Thanks for the help, cant wait to read more of your tutorials

Hi Sam,

I've slowly been working through a good chunk of the tutorials on your site. Each one is written in an easy to understand, concise way that have been a dream to follow. Thanks for the effort you've put into this excellent resource and for sharing it & your follow up in answering questions. Great job.

mark

Hi Sam,
Long time no see. How are you? I just got a newest a post about a instant messenger application. Thanks. I got a problem on apache. It maybe happened on few days ago. I can not visit from the external domain and only be available to visit my website and squirrelmail web page on the internal domain. I use "sudo service apache2 status" to check it out. I got these prompts. It seems there is a depricated settings. The other hand there is a hint of NameVitrualHost no service matter when the next release come. Are these to influence the apache service? If yes, how can I do? Thanks a lot.


● apache2.service - LSB: Apache2 web server
Loaded: loaded (/etc/init.d/apache2)
Drop-In: /lib/systemd/system/apache2.service.d
└─forking.conf
Active: active (running) since Thu 2016-09-29 18:39:22 CST; 37min ago
Process: 3517 ExecStop=/etc/init.d/apache2 stop (code=exited, status=0/SUCCESS)
Process: 3482 ExecReload=/etc/init.d/apache2 reload (code=exited, status=0/SUCCESS)
Process: 3543 ExecStart=/etc/init.d/apache2 start (code=exited, status=0/SUCCESS)
CGroup: /system.slice/apache2.service
├─3559 /usr/sbin/apache2 -k start
├─3566 /usr/sbin/apache2 -k start
├─3567 /usr/sbin/apache2 -k start
├─3568 /usr/sbin/apache2 -k start
├─3569 /usr/sbin/apache2 -k start
├─3578 /usr/sbin/apache2 -k start
├─3581 /usr/sbin/apache2 -k start
├─3599 /usr/sbin/apache2 -k start
├─3604 /usr/sbin/apache2 -k start
├─3605 /usr/sbin/apache2 -k start
└─3606 /usr/sbin/apache2 -k start

Sep 29 18:39:19 raspberrypi apache2[3543]: Starting web server: apache2SecReadStateLimit is depricated, use SecConnReadStateLimit instead.
Sep 29 18:39:19 raspberrypi apache2[3543]: AH00548: NameVirtualHost has no effect and will be removed in the next release /etc/apache2/sites-enabled/default-ssl.conf:2
Sep 29 18:39:22 raspberrypi apache2[3543]: .
Sep 29 18:39:22 raspberrypi systemd[1]: Started LSB: Apache2 web server.

Hi! I'm good thanks, moving house soon which will be interesting with the site hosted at home, I may have to get a VPS temporarily. The first warning is caused by the values in the modsecurity config file, you can change it to the suggested value there if you like. NameVirtualHost used to be required, but isn't any more:
"Prior to 2.3.11, NameVirtualHost was required to instruct the server that a particular IP address and port combination was usable as a name-based virtual host. In 2.3.11 and later, any time an IP address and port combination is used in multiple virtual hosts, name-based virtual hosting is automatically enabled for that address. This directive currently has no effect."
So you can just remove it. Are you saying your config used to work, you didn't change anything, and now it doesn't work the same way any more? How was your squirrelmail config included? Could the original file have been overwritten or changed by a package manager? Sam

Hi Sam,
Wish the new house bring the prosperous for you. I found a package, "ufw" I have been installing on my raspberry pi. This is an misunderstanding. I remove it and the external visiting go well now. To immigrate to VPS is cool. I haven't used VPS service yet. GoDaddy may be the most famous one. But, it seems a service intertwining with virtual machine that the hardware can be arbitrarily used. For economical comparison, is the virtual host much budget due to the hardware architecture sharing? I already have a virtual host on my raspberry pi. I would like to share my IP and boardband to provide a virtual host when you move house this moment.

Jeff

In case you were curious about what that blocked post was in your example, it translates to the following after being decoded. It's not comment spam as you speculated, rather it looks like some kind of attack attempt:


cgi-bin/php?-d allow_url_include=on -d safe_mode=off -d suhosin.simulation=on -d disable_functions="" -d open_basedir=none -d auto_prepend_file=php://input -d cgi.force_redirect=0 -d cgi.redirect_status_env=0 -d auto_prepend_file=php://input -n

Add new comment

The content of this field is kept private and will not be shown publicly.

Filtered HTML

  • Web page addresses and email addresses turn into links automatically.
  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.