Configuring web services (revision 2.1)

Last Modified: 8/12/2021

Introduction

This page describes how Alexis Huxley installed and configured his front-end Apache web server. Important points the configuration described are:

  • each web service (e.g. Subversion, personal web pages, Jira) is contained in its own Apache virtual host, not simply in a different Apache <Location>; this presents fewer client-side password-caching problems for services that do not specify their realm correctly (e.g. OpenProject)
  • for most web services the corresponding virtual hosts proxies to a back-end webserver that runs on a different machine
  • for a few web services (e.g. Subversion) the corresponding virtual host runs on the front-end webserver itself

Basic installation

  1. Run:
    apt -y install apache2
  2. Create a ‘combined2’ logging format, which we will refer to below, by running:
    echo 'LogFormat "%h (%a) %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined2' > /etc/apache2/conf-available/combined2.conf
    a2enconf combined2
    systemctl reload apache2
    
  3. Edit /etc/logrotate.d/apache2 and set:
    /var/log/apache2/*.log /var/log/apache2/*/*.log {
        ...
        rotate 10000
        ...
    
  4. Enable https (albeit without a proper certicate yet) by running:
    a2enmod ssl headers proxy proxy_http proxy_html rewrite xml2enc
    systemctl restart apache2
    nmap localhost

    and verify that ports 80 and 443 are open.

  5. Create a basic template configuration file for http by editing /etc/apache2/sites-available/WEBSITENAME.conf to contain only:
    <VirtualHost *:80>
        ServerName WEBSITENAME
        ServerAdmin webmaster@dont-use-this-address
        ServerSignature off
        CustomLog ${APACHE_LOG_DIR}/WEBSITENAME/WEBSITENAME-access.log combined2
        ErrorLog ${APACHE_LOG_DIR}/WEBSITENAME/WEBSITENAME-error.log
        LogLevel warn
        RedirectMatch permanent /(.*) https://WEBSITENAME/$1
    </VirtualHost>
  6. Create a basic template configuration file for https by editing /etc/apache2/sites-available/WEBSITENAME-ssl.conf to contain only:
    <VirtualHost *:443>
        ServerName WEBSITENAME
        ServerAdmin webmaster@dont-use-this-address
        ServerSignature off
    
        CustomLog ${APACHE_LOG_DIR}/WEBSITENAME/WEBSITENAME-access.log combined2
        ErrorLog ${APACHE_LOG_DIR}/WEBSITENAME/WEBSITENAME-error.log
        LogLevel warn
    
        SSLEngine on
        # Use a self-signed certificate until we have 
        # a LetsEncrypt certificate.
        SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
        SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
        # Keep this commented out until we have a LetsEncrypt certificate.
        #Include /etc/letsencrypt/options-ssl-apache.conf
        #SSLCertificateFile /etc/letsencrypt/live/WEBSITENAME/fullchain.pem
        #SSLCertificateKeyFile /etc/letsencrypt/live/WEBSITENAME/privkey.pem
    </VirtualHost>
  7. Make the current document root into a document root template by running:
    mv /var/www/html /var/www/WEBSITENAME
  8. Remove the factory default http and https sites; we’ll make our own shortly:
    a2dissite 000-default
    a2dissite default-ssl
    systemctl reload apache2

Front-end served websites: empty default vhost

Nothing should access the default vhost since all services that might be accessed are in non-default vhosts. However, I have seen that some programs (particularly on Android) get this wrong. Therefore we set up a default vhost simply in order to stop bad clients going to vhosts that are providing real services.

  1. Set some environment variables; special consideration needs to be made for this website as its name must be alphabetically ahead of all others:
    WEBSITENAME=<name-of-website>       #  e.g. WEBSITENAME=aaa.pasta.freemyip.com
  2. Clone the template components and create the log directory by running:
    cp -ar /var/www/WEBSITENAME /var/www/$WEBSITENAME
    sed "s/WEBSITENAME/$WEBSITENAME/g" \
            < /etc/apache2/sites-available/WEBSITENAME.conf \
            > /etc/apache2/sites-available/$WEBSITENAME.conf
    sed "s/WEBSITENAME/$WEBSITENAME/g" \
            < /etc/apache2/sites-available/WEBSITENAME-ssl.conf \
            > /etc/apache2/sites-available/$WEBSITENAME-ssl.conf
    mkdir /var/log/apache2/$WEBSITENAME
    chown www-data:www-data /var/log/apache2/$WEBSITENAME
    
  3. Because no client should not be specifying a ‘Host:’ HTTP header, then the default host should never be visited (except by hackers). Therefore we can simply block all accesses or use it as a honey trap, etc. I investigated using mod_security, which allows the connection to be dropped. but it log requests as if mod_security was not enabled (e.g. 403 when no DocumentRoot or Location stanzas provided), which is not what I wanted. Sending a 410 status to the client seems the most lightweight action. So add this to the config:
    <Location />
        Redirect 410
    </Location>
  4. Enable the required modules and the website with:
    a2ensite $WEBSITENAME
    a2ensite $WEBSITENAME-ssl
    systemctl reload apache2
  5. Configure HTTPS access as described at Setting up Lets Encrypt (revision 2.1).
  6. To test visit http://<server-ip-address>/.

    Front-end served websites: Subversion

    1. See Configuring Subversion (revision 1.2).

      Proxied websites: Checkmk, I2P, Iplayer, WordPress, …

      1. Set some environment variables:
        WEBSITENAME=<name-of-website>       #  e.g. ...
        WEBSITENAME=checkmk.pasta.freemyip.com
        WEBSITENAME=i2p.pasta.freemyip.com
        WEBSITENAME=radio.iplayer.pasta.freemyip.com
        WEBSITENAME=tv.iplayer.pasta.freemyip.com
        WEBSITENAME=iplayer.pasta.freemyip.com
        WEBSITENAME=jira.pasta.freemyip.com
        WEBSITENAME=judithhabgood.freemyip.com
        WEBSITENAME=suzanneramsay.freemyip.com
        WEBSITENAME=www.pasta.freemyip.com
        WEBSITENAME=nzb.pasta.freemyip.com
        WEBSITENAME=nextcloud.pasta.freemyip.com
        WEBSITENAME=openproject.pasta.freemyip.com
        WEBSITENAME=home.pasta.freemyip.com
        WEBSITENAME=repo.pasta.freemyip.com
        WEBSITENAME=jupyterhub.pasta.freemyip.com
        
      2. Clone the template components and create the log directory by running:
        cp -ar /var/www/WEBSITENAME /var/www/$WEBSITENAME
        sed "s/WEBSITENAME/$WEBSITENAME/g" \
                < /etc/apache2/sites-available/WEBSITENAME.conf \
                > /etc/apache2/sites-available/$WEBSITENAME.conf
        sed "s/WEBSITENAME/$WEBSITENAME/g" \
                < /etc/apache2/sites-available/WEBSITENAME-ssl.conf \
                > /etc/apache2/sites-available/$WEBSITENAME-ssl.conf
        mkdir /var/log/apache2/$WEBSITENAME
        chown www-data:www-data /var/log/apache2/$WEBSITENAME
        
      3. Configure as follows:
        1. If the backend server is running CheckMK then add to /etc/apache2/sites-available/$WEBSITENAME-ssl.conf:
          SSLProxyEngine off
          ProxyPass / http://chifferi.pasta.net:5000/
          ProxyPassReverse / http://chifferi.pasta.net:5000/
        2. If the backend server is running I2P then add to /etc/apache2/sites-available/$WEBSITENAME-ssl.conf:
          SSLProxyEngine off
          <Location />
              ProxyPass http://rombi.pasta.net:7657/
              ProxyPassReverse http://rombi.pasta.net:7657/
              AuthType Basic
              AuthName "I2P Service"
              AuthBasicProvider file
              AuthUserFile /etc/apache2/i2p.htpasswd
              Require valid-user
          </Location>

          and if you want to user LDAP for authentication then replace AuthType ... valid-user with:

          AuthType Basic
          AuthName "Subversion Service"
          AuthBasicProvider ldap
          AuthLDAPUrl ldap://ziti.pasta.net/ou=Users,dc=pasta,dc=net?uid
          Require valid-user
        3. If the backend server is running get_iplayer (radio mode) then add to /etc/apache2/sites-available/$WEBSITENAME-ssl.conf:
          SSLProxyEngine off
          <Location />
              ProxyPass http://farfalle.pasta.net:1935/
              ProxyPassReverse http://farfalle.pasta.net:1935/
              AuthType Digest
              AuthName "iPlayer Service"
              AuthBasicProvider file
              AuthUserFile /etc/apache2/iplayer.htdigest
              Require valid-user
          </Location>
          

          and if you want to user LDAP for authentication then replace AuthType ... valid-user with:

          AuthType Basic
          AuthName "iPlayer Service"
          AuthBasicProvider ldap
          AuthLDAPUrl ldap://ziti.pasta.net/ou=Users,dc=pasta,dc=net?uid
          Require valid-user
        4. If the backend server is running get_iplayer (TV mode) then add to /etc/apache2/sites-available/$WEBSITENAME-ssl.conf:
          SSLProxyEngine off
          <Location />
              ProxyPass http://localhost:1935/
              ProxyPassReverse http://localhost:1935/
              AuthType Digest
              AuthName "iPlayer Service"
              AuthBasicProvider file
              AuthUserFile /etc/apache2/iplayer.htdigest
              Require valid-user
          </Location>
          

          and if you want to user LDAP for authentication then replace AuthType ... valid-user with:

          AuthType Basic
          AuthName "iPlayer Service"
          AuthBasicProvider ldap
          AuthLDAPUrl ldap://ziti.pasta.net/ou=Users,dc=pasta,dc=net?uid
          Require valid-user
        5. If the backend server is running Jira then add to /etc/apache2/sites-available/$WEBSITENAME-ssl.conf:
          SSLProxyEngine off
          ProxyPass / http://girandole.pasta.net:8080/
          ProxyPassReverse / http://girandole.pasta.net:8080/
          ProxyPreserveHost On
        6. If the backend server is running WordPress then add to /etc/apache2/sites-available/$WEBSITENAME-ssl.conf:
          SSLProxyEngine on
          SSLProxyVerify none
          SSLProxyCheckPeerCN off
          SSLProxyCheckPeerName off
          SSLProxyCheckPeerExpire off
          
          ProxyPass / https://vermicelli-judithhabgoodfreemyipcom.pasta.net/
          ProxyPassReverse / https://vermicelli-judithhabgoodfreemyipcom.pasta.net/
          RewriteEngine On
          RewriteRule ^/$ https://vermicelli-judithhabgoodfreemyipcom.pasta.net/index.php [P]
          

          or:

          SSLProxyEngine on
          SSLProxyVerify none
          SSLProxyCheckPeerCN off
          SSLProxyCheckPeerName off
          SSLProxyCheckPeerExpire off
          
          ProxyPass / https://vermicelli-suzanneramsayfreemyipcom.pasta.net/
          ProxyPassReverse / https://vermicelli-suzanneramsayfreemyipcom.pasta.net/
          RewriteEngine On
          RewriteRule ^/$ https://vermicelli-suzanneramsayfreemyipcom.pasta.net/index.php [P]
          

          or:

          SSLProxyEngine on
          SSLProxyVerify none
          SSLProxyCheckPeerCN off
          SSLProxyCheckPeerName off
          SSLProxyCheckPeerExpire off
          
          ProxyPass / https://vermicelli-wwwpastafreemyipcom.pasta.net/
          ProxyPassReverse / https://vermicelli-wwwpastafreemyipcom.pasta.net/
          RewriteEngine On
          RewriteRule ^/$ https://vermicelli-wwwpastafreemyipcom.pasta.net/index.php [P]
        7. If the backend server is running mailman then add to /etc/apache2/sites-available/$WEBSITENAME-ssl.conf:
          SSLProxyEngine on
          SSLProxyCheckPeerCN off
          SSLProxyCheckPeerName off
          ProxyPass / https://marille.pasta.net/
          ProxyPassReverse / https://marille.pasta.net/
        8. If the backend server is running Sabnzbd then add to /etc/apache2/sites-available/$WEBSITENAME-ssl.conf:
          SSLProxyEngine off
          <Location /sabnzbd/>
              ProxyPass http://bigoli.pasta.net:8080/sabnzbd/
              ProxyPassReverse http://bigoli.pasta.net:8080/sabnzbd/
              AuthType Digest 
              AuthName "iPlayer Service"
              AuthBasicProvider file
              AuthUserFile /etc/apache2/iplayer.htdigest
              Require valid-user
          </Location>
          
          

          and if you want to user LDAP for authentication then replace AuthType ... valid-user with:

          AuthType Basic
          AuthName "Sabnzbd Service"
          AuthBasicProvider ldap
          AuthLDAPUrl ldap://ziti.pasta.net/ou=Users,dc=pasta,dc=net?uid
          Require valid-user
        9. If the backend server is running Nextcloud then add to /etc/apache2/sites-available/$WEBSITENAME-ssl.conf:
          SSLProxyEngine on
          SSLProxyCheckPeerCN off
          SSLProxyCheckPeerName off
          ProxyPass / https://nuvole.pasta.net/
          ProxyPassReverse / https://nuvole.pasta.net/
          
        10. If the backend server is running OpenProject then add /etc/apache2/sites-available/$WEBSITENAME-ssl.conf:
          SSLProxyEngine on
          SSLProxyCheckPeerCN off
          ProxyPreserveHost On
          ProxyPass / https://paccheri.pasta.net/
          ProxyPassReverse / https://paccheri.pasta.net/
          

          (‘ProxyPreserveHost’ is needed because otherwise a complaint regarding a mismatch is displayed in OpenProject’s web interface.)

        11. If the backend server is hosting personal web pages then add to /etc/apache2/sites-available/$WEBSITENAME-ssl.conf:
          SSLProxyEngine off
          <Location />
              ProxyPass http://tortelli.pasta.net/
              ProxyPassReverse http://tortelli.pasta.net/
          </Location>
        12. If the backend server is hosting software repositories then add to /etc/apache2/sites-available/$WEBSITENAME-ssl.conf:
          SSLProxyEngine off
          <Location />
              ProxyPass http://tortelli.pasta.net/
              ProxyPassReverse http://tortelli.pasta.net/
          </Location>
        13. If the backend server is running JupyterHub then add to /etc/apache2/sites-available/$WEBSITENAME-ssl.conf:
          # Enable HTTP/2, if available
          Protocols h2 http/1.1
          # HTTP Strict Transport Security (mod_headers is required) (63072000 seconds)
          Header always set Strict-Transport-Security "max-age=63072000"
          
          # Use RewriteEngine to handle WebSocket connection upgrades
          RewriteEngine On
          RewriteCond %{HTTP:Connection} Upgrade [NC]
          RewriteCond %{HTTP:Upgrade} websocket [NC]
          RewriteRule /(.*) ws://pansoti.pasta.net:8000/$1 [P,L]
          
          SSLProxyEngine off
          <Location "/">
              #  preserve Host header to avoid cross-origin problems
              ProxyPreserveHost on
              #  proxy to JupyterHub
              ProxyPass http://pansoti.pasta.net:8000/
              ProxyPassReverse http://pansoti.pasta.net:8000/
              RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
          </Location>
      4. Enable the website with:
        a2ensite $WEBSITENAME
        a2ensite $WEBSITENAME-ssl
        
      5. Repeat the above steps for all websites to be served.
      6. Run:
        systemctl reload apache2
      7. If the firewall and/or DNS needs to be adjusted to direct traffic to the new front-end webserver, then adjust now.
      8. Configure HTTPS access for each website as described at Setting up Lets Encrypt (revision 2.1).

        Enabling selective website redirection

        Do not complete this section unless the section you came from instructed you do to so.

        If there is not a masquerader/firewall between the web browser and the frontend webserver then use this simple method:

        1. Add an entry to a client’s /etc/hosts file to say that the IP address of frontend host has the name of the website, e.g.:
          1.2.3.4 home.pasta.freemyip.com

        If there is a masquerader/firewall between the web browser and the frontend webserver then use this more complicated method:

        1. Make sure that the client host (where the browser will run) has ssh access (client-side user and server-side user are both irrelevant) to the webserver.
        2. If public key authentication is in use then make sure that the entry in authorized_keys on the server does not specify any restrictions (e.g. no-port-forwarding,no-X11-forwarding,no-pty) on the beginning of the key line.
        3. On the host running the web browser run something like:
          ssh -gfND 4445 <new-web-server>
          
        4. On the new web server add an entry to /etc/hosts something like:
          127.0.0.1 <new-web-server>
        5. In the web browser configure the proxy to be localhost:4445 and type SOCKS v5.

        Fancy indexes

        I believe this section is now not to be run on the front end webserver at all, so the multiple references to ‘$WEBSITENAME’ is inappropriate!!! They should be /var/www/html.

        Do not complete this section unless the section you came from instructed you do to so.

        1. Run:
          cd /var/www/$WEBSITENAME
          git clone https://github.com/Vestride/fancy-index
          rm -fr fancy-index/{.git,test}
          mv /etc/apache2/mods-available/autoindex.conf /etc/apache2/mods-available/autoindex.conf.orig
          ln -sr fancy-index/.htaccess /etc/apache2/mods-available/autoindex.conf
        2. Edit /var/www/$WEBSITENAME/fancy-index/script.js and change:
           const parts = path.split('/');
           path = parts[parts.length - 1];
           titleText = titleize(path).replace(/-|_/g, ' ');

          to:

          const parts = path.split('/');
          titleText = path;
          path = parts[parts.length - 1];
          // titleText = titleize(path).replace(/-|_/g, ' ');
          
        3. Edit /var/www/$WEBSITENAME/fancy-index/style.css and change:
           font-size: 0.875rem;

          to:

          /* font-size: 0.875rem; */
          font-size: 1.500rem;

                See also