Managing and organizing Apache virtual host files

Number of perfectly-aligned sticky notes stuck on a table.
Organization is art.

I’m in the midst of building new servers, inventorying the existing ones, and creating a migration plan to move over anything that needs to make it over. This is the perfect time to look at the big picture and assess whether everything is implemented in the best way possible.

One of the items I’m reviewing are the virtual host files, and let me tell you, they’re a mess.

The permissions are all over the place, there doesn’t seem to be a clear naming convention, some virtual host files are purely redirects, and there are so many includes containing additional rules and rewrites, that according to the error logs, they’ve started stepping on each other’s toes (“Request exceeded the limit of 10 internal redirects due to probable configuration error”).

Guidelines

After thinking about the above situation, I came up with a list of guidelines that should be followed when working with virtual host files:

  1. All virtual host files should live in /etc/httpd/vhosts.d.
  2. All virtual host files should have 644 permissions and be owned by root:root.
  3. All virtual host files should be named according to the complete website address, e.g., blog.domain.com.conf.
  4. Each website must be in its own virtual host file, e.g., blog.domain.com.conf, cloud.domain.com.conf.
  5. A virtual host file may contain more than one VirtualHost directive.
  6. A VirtualHost directive should use an asterisk, not a specific IP address, and then be followed by one port number, e.g., <VirtualHost *:80>, <VirtualHost *:443>.
  7. A VirtualHost directive should contain an Include directive to include other directives, like ServerName, DocumentRoot, etc., that may be shared across additional VirtualHost directives for the same website, e.g., Include vhosts.d/includes/blog.domain.com.conf.
  8. The file referenced in the Include directive should have the same name as the master virtual host file and be created in the includes directory, e.g., vhosts.d/includes/blog.domain.com.conf.
  9. The first virtual host file should be called 0.conf and contain all global rules and rewrites, and a default VirtualHost directive that includes a file in the includes directory called default.conf.
  10. Files that are synchronized between multiple servers, like IP whitelists, may be placed within the shared directory.
  11. Other supporting files, like rewrite maps, may be placed within the other directory.
  12. There should be a .template file for a basic website in the vhosts.d and includes directory that can be copied and used as a starting point when creating a virtual host for a new website.

File and Folder Structure

To help reiterate the above guidelines, here is a sample file and folder structure of everything that you might find in the vhosts.d directory:

+-- vhosts.d/
    |-- .template
    |–- 0.conf
    |–- blog.domain.com.conf
    |–- cloud.domain.com.conf
    +-- includes/
    |   |-- .template
    |   |-- blog.domain.com.conf
    |   |-- cloud.domain.com.conf
    |   |-- default.conf
    |   |-- some-other-domain.com.conf
    +-- other/
    |   |-- rewrite_map_master
    +-- shared/
    |   |-- ip_whitelist_error_master
    |   |-- ip_whitelist_redirect_master
    |–- some-other-domain.com.conf

Template Files

The template files serve as a consistent base when creating a virtual host for a new website. While one could manually copy and rename both template files, and then edit them to match the website, I actually use these files within a custom script I created to automatically copy and replace the values. Then, if needed, I can go back and add anything else that’s specific to the website.

Sample of the master virtual host file:

# .template

<VirtualHost *:80>
    Include vhosts.d/includes/FULL_DOMAIN.conf
</VirtualHost>

Sample of the virtual host include file:

# includes/.template

ServerName FULL_DOMAIN
ServerAlias www.FULL_DOMAIN
DocumentRoot /var/www/domains/ROOT_DOMAIN/SUB_DOMAIN/htdocs
ErrorLog "|/usr/local/sbin/cronolog -l /var/www/domains/ROOT_DOMAIN/SUB_DOMAIN/logs/error_log /var/www/domains/ROOT_DOMAIN/SUB_DOMAIN/logs/%Y/%m/%Y-%m-%d-error_log"
CustomLog "|/usr/local/sbin/cronolog -l /var/www/domains/ROOT_DOMAIN/SUB_DOMAIN/logs/access_log /var/www/domains/ROOT_DOMAIN/SUB_DOMAIN/logs/%Y/%m/%Y-%m-%d-access_log" combined

RewriteEngine on
RewriteCond %{HTTP_HOST} ^www\.FULL_DOMAIN$ [NC]
RewriteRule ^/(.*)$ http://FULL_DOMAIN/$1 [R=301,L]

Global Rules and Rewrites

The first virtual host file, 0.conf, and its include file, default.conf, are for rules and rewrites that need to either apply to all virtual hosts, or act as a catch-all for domains that do not have a virtual host file.

Sample of the default master virtual host file:

# includes/default.conf

ServerName server-name.domain.com

RewriteEngine On
RewriteMap redirect_map_master txt:vhosts.d/other/redirect_map_master
RewriteCond %{HTTP_HOST} ^(www\.)?(.+)
RewriteCond ${redirect_map_master:%2|domain.com} ^(.+)$ [NC]
RewriteRule ^/$ http://%1 [L,R=301]

Conclusion

By setting up a few rules and guidelines, and writing proper documentation to outline them, it becomes much easier to manage and overview all of the virtual host files, global rules and redirects.

I hope you find this useful as a starting point, and if you have any questions, issues or recommendations with regard to the approach above, I’d love to hear them in the comments below.

Featured image by Kelly Sikkema.


Comments (2)

Previously posted in WordPress and transferred to Ghost.

Alistair Buxton
February 3, 2017 at 1:49 pm

Not bad, but there are a couple of problems with this method:

1. If you have a lot of domains and subdomains, the directory listing will be all mixed up, which makes it hard to see what is what.

2. If your top level domain has “ServerAlias *.example.com” in example.com.conf, then only subdomains which are alphabetically before the TLD will work. eg: aaa.example.com.conf will work, www.example.com.conf will be shadowed.

The first problem can be solved by using reverse domain name notation, eg com.example.conf. The second one doesn’t seem to have a nice solution other than adding extra stuff to the filename to make it sort properly.

Ryan Sechrest
February 3, 2017 at 2:58 pm

Hi Alex,

For one, I would just run a grep to see everything for a domain. That said, more often than not, I go to manage a specific virtual host. I don’t use this to see what’s setup on the server.

For two, you are right about that. I haven’t run into this issue, because I’m always very explicit in my aliases. Of course that doesn’t work when you’re utilizing subdomains on the fly within your application.

I’ve been following the strategy outlined above for quite some time and it has worked out great, but if you need more granular control, you could put that project’s virtual host files in their own directory and custom order them to meet your needs.