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:
- All virtual host files should live in
/etc/httpd/vhosts.d
. - All virtual host files should have
644
permissions and be owned byroot:root
. - All virtual host files should be named according to the complete website address, e.g.,
blog.domain.com.conf
. - Each website must be in its own virtual host file, e.g.,
blog.domain.com.conf
,cloud.domain.com.conf
. - A virtual host file may contain more than one
VirtualHost
directive. - 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>
. - A
VirtualHost
directive should contain anInclude
directive to include other directives, likeServerName
,DocumentRoot
, etc., that may be shared across additionalVirtualHost
directives for the same website, e.g.,Include vhosts.d/includes/blog.domain.com.conf
. - The file referenced in the
Include
directive should have the same name as the master virtual host file and be created in theincludes
directory, e.g.,vhosts.d/includes/blog.domain.com.conf
. - The first virtual host file should be called
0.conf
and contain all global rules and rewrites, and a defaultVirtualHost
directive that includes a file in theincludes
directory calleddefault.conf
. - Files that are synchronized between multiple servers, like IP whitelists, may be placed within the
shared
directory. - Other supporting files, like rewrite maps, may be placed within the
other
directory. - There should be a
.template
file for a basic website in thevhosts.d
andincludes
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” inexample.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, egcom.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.