Updated on 2021-10-07

Setup a Basic Apache server

Install Apache:

dnf update; dnf install httpd

Start and enable the service:

systemctl start httpd && systemctl enable httpd

Install firewalld and open up port 80:

dnf install firewalld -y
firewall-cmd --permanent --add-service=http
firewall-cmd --reload

Browse to https://localhost or http://server_IP. Your Apache Web Server is up and running.

Note: By default, the Apache Test Page will be presented. Create an index.html file with some HTML in /var/www/html/ and reload the page.

To prevent the test page from ever being used, follow the instructions in the file /etc/httpd/conf.d/welcome.conf.

The structure of /etc/httpd

The /etc/httpd/ directory contains the following(as of this writing):

[root@centos httpd]# ls -l
total 0
drwxr-xr-x. 2 root root  37 Sep 27 17:14 conf
drwxr-xr-x. 2 root root  82 Sep 27 17:14 conf.d
drwxr-xr-x. 2 root root 226 Sep 27 17:14 conf.modules.d
lrwxrwxrwx. 1 root root  19 Sep 15 19:42 logs -> ../../var/log/httpd
lrwxrwxrwx. 1 root root  29 Sep 15 19:42 modules -> ../../usr/lib64/httpd/modules
lrwxrwxrwx. 1 root root  10 Sep 15 19:42 run -> /run/httpd
lrwxrwxrwx. 1 root root  19 Sep 15 19:42 state -> ../../var/lib/httpd
  • conf - The main configuration file httpd.conf

  • conf.d - Additional configuration files for the Apache server. Any file that ends with .conf will be processed as httpd configuration files. By default, it contains the Test Page configuration, and a configuration template on how to serve a user’s home directory.

  • conf.modules.d - Configurations to load a module under an alias or name.

  • logs - The global location for access and error logs.

  • modules - Modules compiled during installation.

  • run - Holds the PID of the server, and its Unix Socket.

  • state - Dynamic/internal data about httpd (the daemon).

Main Configuration file

The main config file is /etc/httpd/conf/httpd.conf. It contains directives or default settings to control the server. There’re a certain set of directives which you may define with different values for multiple Virtual Hosts to override the defaults.

Partial list of the ones mentioned in the config file:

  • ServerRoot - The directory under which the server will contain configuration, error, and log files. The default directory is /etc/httpd.

  • Listen - The IP Address or port the server will serve its content and listen on.

  • IncludeOptional - Includes other configuration files(e.g vhosts or modules) within the ServerRoot.

  • LoadModule - Load a Dynamic module

  • User/Group - Run Apache under another user or group

  • ServerAdmin - Specify an email address, where problems/error messages should be sent.

  • DocumentRoot - The location where you’ll serve the content of your web sites. The default is /var/www/html.

  • DirectoryIndex - Specify a file to serve if the root domain or IP is requested

  • ServerName - Name and port of the server in this format: www.example.com:80. If you don’t have a DNS name, enter its IP address(a static address is preferable). It’s not enabled by default.

Note: You’ll always get this particular message, during a configuration check “httpd: Could not reliably determine the server’s fully qualified domain name”. Just set the ServerName to localhost.

Here’s a list of all the directives.

Directive Context

A Directive is only allowed in a context or in a combination of contexts. The list of contexts:

  • server config - directive allowed in the main server configuration file “httpd.conf”.

  • virtual host - directive allowed inside <VirtualHost>.

  • directory - directive allowed or valid inside <Directory>, <Location>, <Files>, <If>, <Proxy>.

  • .htaccess - directive allowed inside .htaccess file.

Examples:

  • Directive - is allowed in both server config, and virtual host.

  • ServerRoot Directive - is only allowed in server config

Virtual Host

Virtual Hosting is a purpose of serving multiple web sites on a single server. There’re two types of virtual hosts:

  • Name-based - Names-based virtual host is a method of hosting multiple web sites on a single IP address.

  • IP-based - With IP-based, the server has a unique IP address for each web site.

Setup a basic Name-based virtual host

The conventional way to setup a a virtual host, is to place its config inside /etc/httpd/conf.d/. Some users prefer to mimic the environment of debian/ubuntu by creating folders like site-enabled/available.

Note: To use a custom directory, SElinux permissions should be adjusted. I won’t be covering that. Maybe in the future.

I prefer to create a folder called vhost.d, and update the main config file with the following lines to define the new location:

# Load vhosts config files in the "/etc/httpd/vhosts directory"
IncludeOptional /etc/apache/vhost.d/*.conf

Then run httpd -t or apachectl configtest to perform to configuration syntax test. Always run either of these commands after you add/update a config file.

Tip Make a copy of any config file before editing, in case you mess things up.

Start by creating a simple vhost config file, and save it as dev_local.conf:

<VirtualHost *:80>
	ServerName dev.local.com
	ServerAlias dev.local.com
	ServerAdmin admin@local.com
	DocumentRoot /var/www/vhost/dev.local.com/html/
	ErrorLog /var/www/vhost/dev.local.com/log/error.log
	CustomLog /var/www/vhost/dev.local.com/log/access.log combined
</VirtualHost>

The <VirtualHost> directive takes an IP or Port as argument in this format IP:Port. Here it’s listening on port 80 on all IP addresses. Create the specified directories and change the owner to apache:

mkdir -p /var/www/vhost/dev.local.com/{html,log}
chown apache:apache /var/www/vhost/dev.local.com/

Create an index.html file in the html folder:

echo "<p>TEST</p>" > /var/www/vhost/dev.local.com/html/index.html

Update /etc/hosts and browse to dev.local.com.

About SElinux

If SElinux was enabled and its required permissions were not set, you wouldn’t be able to restart apache. Even apachectl status or journalctl doesn’t show the real issue. You can look into /var/httpd/logs/error.log, but there’s no hint about SElinux.

Here’s an example from /var/httpd/logs/error.log:

(13)Permission denied: AH00091: httpd: could not open error log file /var/www/vhost/dev.local.com/log/error.log.

To find the right information, look into /var/log/audit/audit.log and grep for http(partial list):

type=AVC msg=audit(1601535154.263:97): avc:  denied  { append } for  pid=2381 comm="httpd" name="error.log" dev="dm-0" ino=17316040 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:httpd_sys_content_t:s0 tclass=file permissive=0

Directory Directive

The <Directory> tag controls access to a directory(including all its contents). This allows you to serve or deny contents to specific IP ranges, limit access to files, enable or disable directory listing, and so on.

The syntax is <Directory /directory_path/. Here’s an example:

<VirtualHost *:80>
	ServerName example.com
	ServerAlias example.com
	ServerAdmin admin@example.com
	DocumentRoot /var/www/vhost/example.com/html/
	ErrorLog /var/www/vhost/example.com/log/error.log
	CustomLog /var/www/vhost/example.com/log/access.log combined

	<Directory /var/www/vhost/example.com/>
		Options FollowSymlinks
		AllowOverride None
	</Directory>

	<Directory /var/www/vhost/example.com/html/livechat/>
		Require ip 192.168.100 10.10.10
	</Directory>

	<Directory /var/www/vhost/example.com/html/exam/>
		Options +Indexes
	</Directory>

</VirtualHost>

The Options directive is used to enable and disable server features. The FollowSymlinks option allows the webpage to use symbolic links located only in the directory(including subdirectories) specified in the <Directory> tag.

The AllowOverride works alongside .htaccess file. It basically tells the server if whether the Options set in .htaccess can be overridden by the ones set in <Directory>. The default is None, and it’s best to leave it that way.

Require is used to control user access to a website or webpage.

https://serverfault.com/questions/267583/directory-inside-or-outside-virtualhosts https://www.binasss.sa.cr/manual/vhosts/examples.html

Implement TLS

To enable HTTPS, you need to install mod_ssl, and get a certificate from a Certificate Authority. You can create your own with openssl, and or use a Certificate Authority like Let’s Encrypt. It’s free and open-source.

Self Signed Certificate

A virtual host with a basic self-signed certificate is very easy to setup. First, install openssl and mod_ssl:

dnf install openssl mod_ssl -y

Open port 443:

firewall-cmd --permanent --add-port=443/tcp && firewall-cmd reload

Generate SSL key and certificate with openssl. You’ll be prompted to enter some information. You can leave everything blank except the common name:

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/pki/tls/private/dev_tls_com.key -out /etc/pki/tls/certs/dev_tls_com.crt
  • req -x509 : Use x509 certificate signing request(CSR). You can read more about it here.

  • -nodes : Don’t encrypt private keys with a passphrase. A passphrase would prevent apache from starting without entering the passphrase.

  • -days 365 : Number of days to consider the certificate valid.

  • -newkey rsa:2048 : Generate a 2048-bits RSA key(public and private keys). You can also use a 4096 bits RSA key.

  • -keyout : Where to place the private key.

  • -out : Where to place the certificate(the certificate contains the public key and relevant information).

Tip: You can make a dedicated directory like /etc/httpd/conf.d/ssl/domain.com to store your self-signed certificates.

Create a virtual host that’s listening on port 443:

<VirtualHost *:443>
	ServerName dev.tls.com
	ServerAlias dev.tls.com
	ServerAdmin admin@tls.com
	DocumentRoot /var/www/vhost/dev.tls.com/html/
	ErrorLog /var/www/vhost/dev.tls.com/log/error.log
	CustomLog /var/www/vhost/dev.tls.com/log/access.log combined

	SSLEngine on
    SSLCertificateFile /etc/pki/tls/certs/dev_tls_com.crt 
    SSLCertificateKeyFile /etc/pki/tls/private/dev_tls_com.key

</VirtualHost>

# redirect request on port 80 to 443
<VirtualHost *:80>
    		ServerName dev.tls.com
    		Redirect / https://dev.tls.com/
</VirtualHost>
  • SSLEngine - Enable TLS for this virtual host

  • SSLCertificateFile - Certificate or public key

  • SSLCertificateKeyFile - Private key

  • The additional <VirtualHost> - is to listen on port 80 for requests(if any), and redirect all the requests on port 443.

Restart apache and browse to https://dev.tls.com.

Basic Authentication

HTTP Basic Authentication is the most simplest way of implementing access control to web contents. It doesn’t require cookies or anything like that. Just use it for internal purposes.

By convention, you would create a file called .htaccess to configure your login prompt and additional directives. According to the apache documentation:

.htaccess files should be avoided completely if you have access to the main config file. Using .htaccess will slow down the server. Any directive that you can include in a .htaccess file is better set in a Directory or Location block, as it will have the same effect with better performance.

If you use .htaccess file, you can set the following option: AllowOverride All(assuming you’re only using .htaccess for authentication and not addtional configurations.). This way, only authenticated users can access certain features, like directory listing.

My configuration file:

<VirtualHost *:443>
	ServerName test.com
	ServerAlias test.com
	ServerAdmin admin@test.com
	DocumentRoot /var/www/vhost/test.com/html/
	ErrorLog /var/www/vhost/test.com/log/error.log
	CustomLog /var/www/vhost/test.com/log/access.log combined

	SSLEngine on
        SSLCertificateFile /etc/pki/tls/certs/test_com.crt
    	SSLCertificateKeyFile /etc/pki/tls/private/test_com.key

	<Directory /var/www/vhost/test.com/html/>
		AuthType Basic
		AuthName "Restricted"
		AuthUserFile /var/www/vhost_htpasswd/.htpasswd_test.com
		Require valid-user
	</Directory>

</VirtualHost>

<VirtualHost *:80>
    		ServerName test.com
    		Redirect / https://test.com/
</VirtualHost>

Note: For security reason, .htpasswd files should be stored in /etc/httpd or /etc/apache2. I don’t think there are any official best practices for this, correct me if I am wrong.

Create the htpasswd file(-c), create an account for user testadmin, and set the required permissions:

mkdir /var/www/vhost_htpasswd
htpasswd -c /var/www/vhost_htpasswd/.htpasswd_test.com testadmin # prompt for password
chown apache:apache /var/www/vhost_htpasswd/.htpasswd_test.com
chmod 600 !$ 

Note: You can keep .htpasswd in your website’s document root.

Restart apache, and browse to test.com.

With Location

The <Location> directive only works for URLs. The following configuration will prompt for a pair of credentials to gain access to domain.com/server-status, and is only available for the specified IP range.

<Location /server-status> 
	SetHandler server-status # from mod_status
	Require ip 192.168.100.0/24 
	AuthType Basic
	AuthName "Restricted" 
	AuthUserFile /var/www/domain.com/.htpasswd
	Require valid-user
</Location>

LAMP Stack

The LAMP stack consists of:

  • Linux - the OS
  • Apache - the http Server
  • MariaDB - the database to store backend data
  • and PHP - for dynamic processing

Install MariaDB

dnf install mariadb-server -y

Start and enable the service:

systemctl start mariadb ; systemctl enable mariadb

Run the secure installation script:

mysql_secure_installation

Install PHP and its MySQL module:

dnf install php php-mysqlnd -y

Restart apache:

systemctl restart httpd

Test PHP

Create a file called test.php in your main or vhost document root:

<? php
phpinfo();
?>

Browse to YOUR_DOMAIN/test.php

Test MariaDB

Log into mariadb(if you didn’t set a password just run mysql):

mysql -u root -p

Create a new database called phptest:

CREATE DATABASE phptest;

Create a new user with full privileges to the newly create database:

GRANT ALL ON test.* TO 'sqltest'@'localhost' IDENTIFIED BY 'sqltest' WITH GRANT OPTION;

Both Username and Password are set to “sqltest”. The user won’t be able to create or modify other databases.

Save the changes and exit:

FLUSH PRIVILEGES;
exit;

Log in as the new user:

mysql -u sqltest -p

Use the phptest database and create a new table:

USE test;
CREATE TABLE test_list(id INT AUTO_INCREMENT, whisky VARCHAR(255), PRIMARY KEY(id));

Insert some data:

INSERT INTO test_list (whisky) VALUES ("Jack Daniel's Old No.7");
INSERT INTO test_list (whisky) VALUES ("Monkey Shoulder Blended Malt Scotch");

Confirm the data:

SELECT * FROM test_list;

Exit, and create the following index.php in your document root:

<?php
$link = mysqli_connect("127.0.0.1", "sqltest", "sqltest", "phptest"); // host, username, passwd, db

if (!$link) {
    echo "Error: Unable to connect to MySQL." . PHP_EOL;
    echo "Debugging errno: " . mysqli_connect_errno() . PHP_EOL;
    echo "Debugging error: " . mysqli_connect_error() . PHP_EOL;
    exit;
}

echo "Success: A proper connection to MySQL was made! The database is great." . PHP_EOL;
echo "Host information: " . mysqli_get_host_info($link) . PHP_EOL;

mysqli_close($link);
?>

The script was found here. Browse to YOURDOMAIN/index.php:

Success: A proper connection to MySQL was made! The database is great. Host information: 127.0.0.1 via TCP/IP

Require directive Cheatsheet

https://apacheadmin.com/require/

Debian Based

Installation

apt install apache2

Configuration Files

kavish@ubuntu-local:/etc/apache2$ ls -l
total 80
-rw-r--r-- 1 root root  7244 Mar 23 07:10 apache2.conf
drwxr-xr-x 2 root root  4096 Mar 24 08:11 conf-available
drwxr-xr-x 2 root root  4096 Mar 24 08:11 conf-enabled
-rw-r--r-- 1 root root  1782 Apr 13  2020 envvars
-rw-r--r-- 1 root root 31063 Apr 13  2020 magic
drwxr-xr-x 2 root root 12288 Mar 23 07:08 mods-available
drwxr-xr-x 2 root root  4096 Mar 23 07:08 mods-enabled
-rw-r--r-- 1 root root   320 Apr 13  2020 ports.conf
drwxr-xr-x 2 root root  4096 Mar 23 07:08 sites-available
drwxr-xr-x 2 root root  4096 Mar 23 07:08 sites-enabled
  • apache2.conf - Main configuration file.
  • conf-available - Contains configuration files not associated with virtual hosts.
  • conf-enabled - When Configuration files from conf-available are enabled, they will be symlinked here.
  • envvars - Environment variables for the internal Apache structure. Not the ones found on a Linux system.
  • magic - A text file that contains instruction for determining MIME type of a file, or simply a file type like, jpeg, mpeg, and so on.
  • mods-available - Contains configuration files for available modules.
  • mods-enabled - When modules from mods-available are enabled, they will be symlinked here.
  • ports.conf - Determine the ports Apache2 is listening on. For both HTTP and HTTPS.
  • sites-available - Contains Virtual Hosts Configurations.
  • sites-enabled - When Virtual Hosts from conf-available are enabled, they will be symlinked here.

Additional Commands

kavish@ubuntu-local:/usr/sbin$ ls a2*
a2disconf  a2dismod   a2dissite  a2enconf   a2enmod    a2ensite   a2query
  • a2disconf - Disable configuration files in conf-enabled.
  • a2dismod - Disable modules in mods-enabled
  • a2dissite - Disable virtual hosts in sites-enabled
  • a2enconf - Enable configuration files from conf-available by creating a symlink in conf-enabled.
  • a2enmod - Enable modules
  • a2ensite - Enable virtual hosts
  • a2query - Get information about available modules, if sites are enabled, and so on.

VirtualHost Configuration

If you don’t remember all the syntax required to create a vhost configuration, you can copy the default template, edit it according to your needs:

$ cd /etc/apache2/sites-available/
$ cp 000-default.conf test.com.conf

Remove all the comments from the file:

sed -i '/^[[:blank:]]*#/d;s/#.*//' test.com.conf

My virtual host file looks like this:

<VirtualHost *:80>

	ServerAdmin admin@localhost
	ServerName test.com
	ServerAlias www.test.com
	DocumentRoot /var/www/vhosts/test.com


	ErrorLog ${APACHE_LOG_DIR}/test.com_error.log
	CustomLog ${APACHE_LOG_DIR}/test.com_access.log combined

</VirtualHost>

Create the DocumentRoot, and give it the required permissions:

$ mkdir vhosts
$ chown www-data:www-data vhosts/
$ mkdir test.com

Create an index.html file in the DocumentRoot:

<h1>test.com is up and running</h1>

Enable the vhost, disable the default test page, and restart apache2:

$ a2ensite test.com.conf
$ a2dissite 000-default.conf
$ systemctl reload apache2

Edit /etc/host, and browse to the site:

kavish@ubuntu-local:~$ curl test.com
<h1>
test.com is up and running
</h1>

Disable Apache Signature and Banner

ServerSignature Off
ServerTokens Prod

Disable Directory Indexing

<Directory /var/www/html>
  Options -Indexes
</Directory>

Turn on directory browsing

<Location /images>
  Options +Indexes
</Location>

Restrict access to “root” Directory

<Directory />
  Options None
  AllowOverrride None
  Order allow,deny
  Allow from all
</Directory>

Custom 404 Error message

ErrorDocument 404 /404.html

Only allow access from a specific IP

Order Deny,Allow
Deny from all
Allow from 127.0.0.1

Only allow access for a subnet

Order Deny,Allow
Deny from all
Allow from 176.16.0.0/16

ModSecurity

https://www.youtube.com/watch?v=MB7nQrlP5Yc

https://www.atlantic.net/vps-hosting/securing-your-apache-web-server-with-mod-security/

https://www.linuxbabe.com/security/modsecurity-apache-debian-ubuntu

Benchmark - Load Test with ab

http://httpd.apache.org/docs/current/programs/ab.html

Check HTTPD resource limits

https://code.google.com/archive/p/check-httpd-limits/wikis/Documentation.wiki

Common Errors

https://www.digitalocean.com/community/tutorial_series/common-apache-errors

Rewrite Rules

Web Log Analysis