Managing and organizing Apache virtual host files

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. 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. 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:

1
2
3
4
5
# .template
 
<VirtualHost *:80>
    Include vhosts.d/includes/FULL_DOMAIN.conf
</VirtualHost>
# .template

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

Sample of the virtual host include file:

1
2
3
4
5
6
7
8
9
10
11
# 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]
# 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 0.conf
 
# Set environment variables
SetEnv APP_ENVIRONMENT PRODUCTION
 
# Prevent public access to Git directories and files
RedirectMatch 404 /\.git
 
# Prevent access to WordPress login
<Files wp-login.php>
    Order deny,allow
    Deny from all
    Include vhosts.d/shared/ip_whitelist_error_master
    ErrorDocument 403 /
</Files>
 
# Default virtual host
<VirtualHost *:80>
    Include vhosts.d/includes/default.conf
</VirtualHost>
# 0.conf

# Set environment variables
SetEnv APP_ENVIRONMENT PRODUCTION

# Prevent public access to Git directories and files
RedirectMatch 404 /\.git

# Prevent access to WordPress login
<Files wp-login.php>
	Order deny,allow
	Deny from all
	Include vhosts.d/shared/ip_whitelist_error_master
	ErrorDocument 403 /
</Files>

# Default virtual host
<VirtualHost *:80>
	Include vhosts.d/includes/default.conf
</VirtualHost>

Sample of the default virtual host include file:

1
2
3
4
5
6
7
8
9
# 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]
# 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.

Leave a Reply

Your email address will not be published. Required fields are marked *