A web server is a practical thing to install on your router. Mixing server applications on the same physical computer that runs the firewall is generally a bad idea security wise but since this is a hobby project for your home LAN and not something that protects a huge corporate business, you can allow yourself some degree of freedom. You should still be concerned with security of course and be aware of the fact that there will always be someone trying to hack into your system.
Apache is used in millions of web server installations around the world and is the preferred choice for this project. It's extremely flexible and powerful which of course will reflect in the complexity of its configuration files, but don't be scared - as with all the guides here I will try to stay as close to the default FreeBSD configuration as possible and it's surprisingly few options you have to alter. You will however need to create some folder structure to hold the contents of your web sites. I will show you one way to do it in this guide but there is no single "right" way so you can of course do it any way that suits your purpose the best.
# cd /usr/ports/www/apache22 # make # make install clean
While you're at it you should also install sysutils/cronolog to help you rotate the logs from Apache. This program will provide an options screen too but leave them at their default values.
# cd /usr/ports/sysutils/cronolog # make # make install clean
Before you look at the configuration of Apache you have to prepare a few other things first.
Apache supports virtual hosts, which means that you can have more than one web site on the same physical server. Look at this example:
# host pp.dyndns.biz pp.dyndns.biz has address 220.127.116.11 # host morgan.wesström.se morgan.wesström.se is an alias for pp.dyndns.biz. pp.dyndns.biz has address 18.104.22.168
You can see that both domains point to the same IP address. The second domain is an alias to the first but if you browse to them they will reveal two separate web sites despite the fact that they reside on the same server. You can have as many different domains as you like, all pointing to your router, and Apache will serve each domain its own web contents. It can do this because the domain name is part of the http request the client's browser is sending so Apache knows what domain the client wants. In your configuration you will define all domains and what folder their web contents is stored in. The default location for web contents is /usr/local/www/apache22/data but this is insufficient and you need a more clever folder structure to support your virtual hosts.
Add a "web" user
Start by adding a new user to your system. You'll let this user's home directory act as the storage place for your web contents and by giving this user limited privileges you also add a bit more security to your configuration. The command to add a new user is logically adduser and you can of course choose another name for your user. Just remember to always use lower case letters for the userid.
# adduser Username: myweb Full name: myweb Uid (Leave empty for default): Login group [myweb]: Login group is myweb. Invite myweb into other groups? : Login class [default]: Shell (sh csh tcsh nologin) [sh]: Home directory [/home/myweb]: Use password-based authentication? [yes]: Use an empty password? (yes/no) [no]: Use a random password? (yes/no) [no]: Enter password: Enter password again: Lock out the account after creation? [no]:
Adduser will now list all input and ask for confirmation.
Username : myweb Password : ***** Full Name : myweb Uid : 1003 Class : Groups : myweb Home : /home/myweb Shell : /bin/sh Locked : no OK? (yes/no): yes adduser: INFO: Successfully added (myweb) to the user database. Add another user? (yes/no): no Goodbye!
In case anyone would send email to this user you should add it to the aliases database and have those emails directed to yourself instead so you don't have to monitor this mailbox. Substitute the userids below to match your own system.
# cd /etc/mail # echo "myweb: pp" >> /etc/mail/aliases # make
Creating a folder structure
Open a second ssh session to your router and logon with the account you just created. Since you're going to create the folder structure to hold your web contents, it's very important to use this regular user so that the file permissions will be correct from the beginning. Whenever you work with the files and folders on your web sites, this is the account to use.
I will now show an example of how to create the layout for two different domains. The same scheme can be used for any number of domains and sub-domains of course. First make sure you're in the home folder of your web user:
[myweb]$ cd [myweb]$ pwd /home/myweb
Now for the actual folders. I'll make six of them in this example which corresponds to four different virtual web sites and a log folder for each of the two domains. The -p parameter to mkdir automatically creates intermediate directories if they're missing and the strange looking domain name is the punycode version of the IDN domain used earlier in this example.
[myweb]$ mkdir -p /home/myweb/public_html/pp.dyndns.biz/www [myweb]$ mkdir -p /home/myweb/public_html/pp.dyndns.biz/test [myweb]$ mkdir -p /home/myweb/public_html/pp.dyndns.biz/logs [myweb]$ mkdir -p /home/myweb/public_html/xn--wesstrm-f1a.se/www [myweb]$ mkdir -p /home/myweb/public_html/xn--wesstrm-f1a.se/morgan [myweb]$ mkdir -p /home/myweb/public_html/xn--wesstrm-f1a.se/logs
You'll be using the www folders as the main folders of your domains, regardless of whether you use www in the url or not. This folder will also catch all other attempts to access subdomains that doesn't exist. When you're finished, the folder structure should look like this. (You can make these nice looking directory listings if you install sysinstall/tree.)
home └── myweb └── public_html ├── pp.dyndns.biz │ ├── logs │ ├── test # http://test.pp.dyndns.biz/ │ └── www # http://pp.dyndns.biz/ or http://www.pp.dyndns.biz/ └── xn--wesstrm-f1a.se ├── logs ├── morgan # http://morgan.wesström.se/ └── www # http://wesström.se/ or http://www.wesström.se/
Again, this is just a suggestion of how to structure your web sites but it's very ordered and you can see similar layouts in use at large data centers.
While you're still logged on as myweb, create a unique index.html in each virtual web root folder so we can verify that our Apache configuration is working later on.
[myweb]$ echo "test.pp.dyndns.biz" > /home/myweb/public_html/pp.dyndns.biz/test/index.html [myweb]$ echo "pp.dyndns.biz" > /home/myweb/public_html/pp.dyndns.biz/www/index.html [myweb]$ echo "morgan.wesström.se" > /home/myweb/public_html/xn--wesstrm-f1a.se/morgan/index.html [myweb]$ echo "wesström.se" > /home/myweb/public_html/xn--wesstrm-f1a.se/www/index.html
The main configuration file for Apache is /usr/local/etc/apache22/httpd.conf and it has a tremendous amount of configuration options and flexibility. The official Apache documentation would be a good place to start to learn about all the details. For now, you will only need to change a few options.
You can see in this file that the default web root location is /usr/local/www/apache22/data but you won't be using it since you have created your own folder structure elsewhere. This folder also has the potential of being overwritten by an update so even if you only need one web root you should put your files some place else. The same goes for the log files which are put in /var/log by default. You'll define separate log files for each virtual host later so nothing should be written to these log files except for some occasional startup or stop message.
The ServerAdmin directive should point to webmaster. You already have a webmaster alias defined in Sendmail so you don't have to change any mail configuration to use it. Any mail sent to email@example.com will be forwarded to your regular user account on the router.
Change it to point to the webmaster on your own domain:
As recommended in the configuration file you should also define ServerName to point to your DynDNS domain.
Remove the # and change it to your own domain:
You also need to include another configuration file where the virtual hosts will be defined. In principle you could put all the host definitions here in the main configuration file but it tends to grow big so splitting the settings in separate files makes it easier for you to manage.
Remove the #:
The very first directive in this file is NameVirtualHost *:80 which activates the name-based virtual hosting feature. Each virtual host definition is then contained within a VirtualHost directive and you can repeat almost any other directive from the main configuration file here and it will take precedence. The values from the main configuration file will be used as defaults if they're left out here. There are two examples in this file and you have to remove them before continuing.
<VirtualHost *:80> ServerAdmin firstname.lastname@example.org DocumentRoot "/usr/local/docs/dummy-host.example.com" ServerName dummy-host.example.com ServerAlias www.dummy-host.example.com ErrorLog "/var/log/dummy-host.example.com-error_log" CustomLog "/var/log/dummy-host.example.com-access_log" common </VirtualHost> <VirtualHost *:80> ServerAdmin email@example.com DocumentRoot "/usr/local/docs/dummy-host2.example.com" ServerName dummy-host2.example.com ErrorLog "/var/log/dummy-host2.example.com-error_log" CustomLog "/var/log/dummy-host2.example.com-access_log" common </VirtualHost>
Replace them with a definition for your main DynDNS domain.
<VirtualHost *:80> DocumentRoot "/usr/home/myweb/public_html/pp.dyndns.biz/www" ServerName pp.dyndns.biz ErrorLog "|/usr/local/sbin/cronolog /usr/home/myweb/public_html/pp.dyndns.biz/logs/httpd-error.%Y%m.www.log" CustomLog "|/usr/local/sbin/cronolog /usr/home/myweb/public_html/pp.dyndns.biz/logs/httpd-access.%Y%m.www.log" combinedio <Directory "/usr/home/myweb/public_html/pp.dyndns.biz/www"> Options Indexes FollowSymLinks AllowOverride None Order allow,deny Allow from all </Directory> </VirtualHost>
Let's have a closer look at it. The DocumentRoot and ServerName are very important. When a client is requesting a web page from your server, Apache will look at the url and pick out the server name. It then looks up that name in this file and serves the client the contents located in the folder defined by DocumentRoot. They are the fundamental directives that match your domain with the physical location of your web contents on the server.
The log definitions look a little different from the ones in httpd.conf. Web server logs, particularly access logs, grow big really fast. Now and then you need to rotate them or delete them. There is functionality for this in FreeBSD you could use but if you rename or delete a log file you also have to restart Apache afterwards for the logging to continue. FreeBSD's log rotate functionality could take care of that too but if you have several virtual domains with individual logs, you have to restart Apache once for each log you rotate. Setting up a schedule for this, that won't accidentally try to restart Apache multiple times at the exact same point in time, will be a challenge. Web services will also be offline while Apache restarts which isn't desirable and this is where cronolog can help. Apache has the ability to pipe the output from the logging engine to anywhere you wish so you make use of that feature and pipe them to cronolog which in turn accepts arguments for creating the names of the log files. %Y%m in this example will make the file names contain the year and month so when the current month turns into the next, cronolog will simply close the old file and create a new one while buffering any output from Apache in the mean time. Then it will continue logging and Apache will never have to be restarted thanks to this simple little application. No other log rotating mechanism is needed. If you wish, you can use other combinations of arguments in the file names, like /%Y/%m/%d/ which would create subfolders with the year, month and day and have the log files put there.
The www after the date argument is there to identify which domain the log file belongs to. Since this example put all log files from your domain and its subdomains in the same folder, you'll need a label to tell them apart. The combinedio log format is preferred for statistics since it has more details than the default common log format.
Last in this VirtualHost directive you also have to define the permissions for the web root folder of this virtual hosts. You simply use the same permissions here that Apache use for its default root folder in httpd.conf. The permissions will be inherited to any subfolders but if you forget this part, your clients won't be able to access any contents in this folder or its subfolders. The Indexes argument will serve a folder listing to the client if there is no index.html available. This could be useful in some circumstances but for security reasons you should remove that option before you grant too many people access to your server.
For the three remaining virtual hosts you can use these VirtualHost directives as a template.
<VirtualHost *:80> DocumentRoot "/usr/home/myweb/public_html/pp.dyndns.biz/test" ServerName test.pp.dyndns.biz ErrorLog "|/usr/local/sbin/cronolog /usr/home/myweb/public_html/pp.dyndns.biz/logs/httpd-error.%Y%m.test.log" CustomLog "|/usr/local/sbin/cronolog /usr/home/myweb/public_html/pp.dyndns.biz/logs/httpd-access.%Y%m.test.log" combinedio <Directory "/usr/home/myweb/public_html/pp.dyndns.biz/test"> Options Indexes FollowSymLinks AllowOverride None Order allow,deny Allow from all </Directory> </VirtualHost> <VirtualHost *:80> DocumentRoot "/usr/home/myweb/public_html/xn--wesstrm-f1a.se/www" ServerName xn--wesstrm-f1a.se ErrorLog "|/usr/local/sbin/cronolog /usr/home/myweb/public_html/xn--wesstrm-f1a.se/logs/httpd-error.%Y%m.www.log" CustomLog "|/usr/local/sbin/cronolog /usr/home/myweb/public_html/xn--wesstrm-f1a.se/logs/httpd-access.%Y%m.www.log" combinedio <Directory "/usr/home/myweb/public_html/xn--wesstrm-f1a.se/www"> Options Indexes FollowSymLinks AllowOverride None Order allow,deny Allow from all </Directory> </VirtualHost> <VirtualHost *:80> DocumentRoot "/usr/home/myweb/public_html/xn--wesstrm-f1a.se/morgan" ServerName morgan.xn--wesstrm-f1a.se ErrorLog "|/usr/local/sbin/cronolog /usr/home/myweb/public_html/xn--wesstrm-f1a.se/logs/httpd-error.%Y%m.morgan.log" CustomLog "|/usr/local/sbin/cronolog /usr/home/myweb/public_html/xn--wesstrm-f1a.se/logs/httpd-access.%Y%m.morgan.log" combinedio <Directory "/usr/home/myweb/public_html/xn--wesstrm-f1a.se/morgan"> Options Indexes FollowSymLinks AllowOverride None Order allow,deny Allow from all </Directory> </VirtualHost>
As you can see, the structure is identical in each section. You only have to change any reference to the domain name and the folders involved.
Add a firewall rule
To have the web server accessible from the Internet, you'll need to add a rule to your firewall allowing that traffic to pass. Find the rule allowing ssh in the section # Incoming to router and add this line after it:
pass in quick on $ext_if inet proto tcp from any to ($ext_if) port http flags S/SA synproxy state queue (q_p1, q_p2)
When you have saved the file you need to reload the firewall rules as usual.
# pfctl -nf /etc/pf.conf # pfctl -F rules ; pfctl -f /etc/pf.conf
You probably want the web server to start automatically when you start the router.
# echo 'apache22_enable="YES"' >> /etc/rc.conf # echo 'apache22_http_accept_enable="YES"' >> /etc/rc.conf
The second line adds a performance enhancement which is supported in FreeBSD. It's a kernel module that will buffer most http requests until they're complete, before sending them to the server. It will avoid lots of context switching.
Before starting Apache manually for the first time you should let it check the config files for any errors.
# /usr/local/etc/rc.d/apache22 configtest
If you receive an OK you can go ahead and start the server.
# /usr/local/etc/rc.d/apache22 start
The final test now would be to browse to each of your virtual domains and see that the expected web page is served.
.htaccess is yet another file where you can place Apache directives. It's normally used by users who don't have access to the main server configuration file to alter various aspects of their own, designated web space. Popular use include blocking IP addresses and rewrite urls on-the-fly, preventing hot-linking of pictures for example. It's also a very convenient way for an administrator to change settings on folder level without having to bother with httpd.conf and I will show an example here how to password protect the virtual host called test.pp.dyndns.biz. However, by default the use of .htaccess is prohibited by the AllowOverride directive in the virtual host configuration section. Load the httpd-vhosts.conf file and scroll down to the section defining test.pp.dyndns.biz.
Change it to:
Or, if you're paranoid about security, you could specifically list what directives should be allowed in the .htaccess file. Save the config file and restart the Apache server.
# /usr/local/etc/rc.d/apache22 restart
From now on you once again need to be logged on as your web user myweb to perform the remaining actions. Start by creating the actual .htaccess file and it have to be placed in the web root of test.pp.dyndns.biz.
[myweb]$ ee /usr/home/myweb/public_html/pp.dyndns.biz/test/.htaccess
Copy and paste the following contents and then save the file.
AuthUserFile /usr/home/myweb/public_html/.htpasswd AuthName "This is a secret area!" AuthType Basic require valid-user
The first row points to the file where you will store the userids of the people you wish to grant access to this particular web site. require valid-user means that anyone in .htpasswd is granted access but you could easily replace valid-user with one or more specific userids from that file. .htpasswd should be placed outside your web root folder so there's no way to access it through a browser. Since it contains passwords it's encrypted and needs to be created with a special application which is conveniently called htpasswd.
[myweb]$ htpasswd -c /usr/home/myweb/public_html/.htpasswd pp New password: Re-type new password: Adding password for user pp
To add another user simply run the same command again but omit the -c switch. If you try to logon to the protected web site now you should be greeted with a login prompt.
- /usr/local/etc/apache22/httpd.conf is the main configuration file for Apache.
- /usr/local/etc/apache22/extra/httpd-vhosts.conf is the configuration file for the virtual hosts.
- /usr/local/etc/rc.d/apache22 is the start/stop script for Apache.
- Permissions on web contents should be 644 for files and 755 for directories.
- Being able to use SSL with the server would be nice but then we're back in the land of encryption where I don't feel very comfortable. Let's see if the future can bring a good guide for this.
- Security could be much tighter of course. It's difficult to find any "best practices" so I haven't deviated from FreeBSD's default here but a nice security overhaul wouldn't hurt.