Multiple domains, rewrite rules and localhost

Published at , modified at , by Raffael Jesche, categorized as:

So yesterday I finally published this shiny, new website. After weeks of work to tell 11ty to match (most of) my expactations it felt like a relief. But a new problem came up: redirecting some of my other domains to this site.

As mentioned in my last post (de), I have a small collection of domains. One is only used for mails: raffaeljesche.de. Another one from my first website verstyler.de, abandoned for a few years and re-acquired recently, collects dust, too. Both should redirect to rotatio.de now.

The following text is very technical. I expect you to know about the basics of:

Now let’s move on (if you’re still interested).

Options

  • INWX url redirect in DNS settings
    • This is not real DNS. It just looks like it in the user interface.
    • Causes missing SSL certificate issues when typing https://example.com (with s) directly in the browser address bar. Just typing example.com without protocol hides this problem, because the first redirect on an INWX server is unencrypted.
  • point domains (with and without www.) to Uberspace
    • Uberspace takes care of Let’s Encrypt certificates automatically (Uberspace docs: https
    • causes duplicated content if not redirected properly

My old strategy

Uberspace has one real document root in /var/www/virtual/$USER/html/. By default, all domains point to this directory. If a folder /var/www/virtual/$USER/example.com exists, the site is served from over there. $_SERVER['DOCUMENT_ROOT'] still points to the real document root /var/www/virtual/$USER/html/. This can cause issues for some applications and may need a RewriteBase / in .htaccess, but all my files are static html files, so I don’t care. Apache can take care of the domain redirects by adding a .htaccess to all (sub) domain directories on my Uberspace.

.htaccess file:

# redirect all other domains (e. g. $USER.uber.space, www subdomain)
RewriteEngine On
RewriteCond %{HTTP_HOST} !=rotatio.de
RewriteRule (.*) https://rotatio.de/$1 [R=301,L]

Folder setup:

# https://rotatio.de (synced with 11ty output directory)
/var/www/virtual/$USER/html/

# https://www.rotatio.de
/var/www/virtual/$USER/www.rotatio.de/.htaccess

# https://rotatio.uber.space (default Uberspace user domain)
/var/www/virtual/$USER/rotatio.uber.space/.htaccess

This setup can be simplified by creating only one folder and creating symlinks for all domains to that directory. This strategy is nice, because I don’t have do deal with modifying .htaccess files provided by a CMS (which might change with an update unexpectedly).

When pointing some more (outdated) domains to this setup, it becomes messy:

# https://raffaeljesche.de
/var/www/virtual/$USER/raffaeljesche.de/.htaccess

# https://www.raffaeljesche.de
/var/www/virtual/$USER/www.raffaeljesche.de/.htaccess

# https://verstyler.de
/var/www/virtual/$USER/verstyler.de/.htaccess

# https://www.verstyler.de
/var/www/virtual/$USER/www.verstyler.de/.htaccess

Now I don’t use a CMS and I have full control on my settings. So I thought, I could skip the manual process of creating all these empty folder duplicates, that only exist for redirecting.

The problem with .htaccess

It’s complicated.

Sadly, while researching, Cloudflare was down (again) and with it half of the internet. Multiple search results weren’t accessible and the htaccess testing tool suddenly stopped working because it’s API requests were down too.

Some useful information from a Stack Overflow post didn’t work for me, but at least their server was online. And I found another thread on Stack Overflow with many tips for debugging .htaccess files.

Luckily I remembered, that I solved this problem in the past already–and I found a note from 2020. It needed some refactoring, but it showed me the missing piece: I forgot to match the port number while testing. So checking against localhost etc. always failed while running on http://localhost:8080.

Changes to my old note:

  • don’t use 301 for all domains, only for www subdomain
  • use 302 redirect instead for other domains
  • check against 127.x.x.x range instead of 127.0.0.1
  • added check against 192.168.x.x range

The final config:

<IfModule mod_rewrite.c>
	RewriteEngine on

	# Redirect all other domains (e. g. $USER.uber.space, www subdomain)
	# Checking for HTTPS or port 443 is not needed, because Uberspace doesn't
	# support unencrypted connections and they renew Let's Encrypt certificates
	# automatically.

	# 301 for www subdomain
	RewriteCond %{HTTP_HOST} =www.rotatio.de
	RewriteRule ^(.*)$ https://rotatio.de/$1 [R=301,L]

	# 302 for all other domains pointing to this server
	RewriteCond %{HTTP_HOST} !=rotatio.de
	# but not on localhost (with optional port)
	# The following conditions could be merged into one unreadable regex.
	RewriteCond %{HTTP_HOST} !^localhost(?::\d+)?$
	# still on localhost, but covering the whole 127.x.x.x range
	RewriteCond %{HTTP_HOST} !^127\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}(?::\d+)?$
	# and not while exposing dev machine to (W)LAN for cross device testing
	# covering 192.168.x.x range
	RewriteCond %{HTTP_HOST} !^192\.168\.[0-9]{1,3}\.[0-9]{1,3}(?::\d+)?$
	RewriteRule ^(.*)$ https://rotatio.de/$1 [R=302,L]
</IfModule>

Now my document roots are clean. All domains point to the html/ directory and are handled with a single .htaccess file.

Nice!

Of course, the whole file is a bit longer. You can inspect the .htaccess source code on Codeberg.

And don’t forget: Never set a 301 redirect before all your tests are finished.