Apache#
Integration Details |
|
Ingester |
|
Kit |
Apache Configuration#
Apache defines its log locations via the ErrorLog and CustomLog directives, which can be configured globally (typically /etc/apache2/apache2.conf) or per virtual host (typically /etc/apache2/sites-available/{VHOST_NAME}.conf). By default, these write to error.log and access.log in /var/log/apache2/. Follow the steps below to configure Apache to output clean data for Gravwell ingestion, then install File Follower on your Apache host by following the instructions in File Follower. Then add the configuration from the File Follower section below.
Define LogFormat#
Apache uses the Common Log Format by default. Define a custom format named json_combined that writes the logs cleanly as JSON before ingestion into Gravwell.
You can define this globally in /etc/apache2/apache2.conf or inside a specific Virtual Host in /etc/apache2/sites-available/{VHOST_NAME}.conf.
LogFormat \
"{\"time\":\"%{%Y-%m-%dT%H:%M:%S}t\",\
\"remote_addr\":\"%a\",\
\"method\":\"%m\",\
\"uri\":\"%U\",\
\"query\":\"%q\",\
\"status\":%>s,\
\"bytes\":%B,\
\"duration\":%D,\
\"user_agent\":\"%{User-Agent}i\",\
\"referer\":\"%{Referer}i\",\
\"vhost\":\"%v\"}" json_combined
Key Field Mechanics#
%auses the client IP address (or the address decoded bymod_remoteip, if it is enabled).%Dreports the request duration in microseconds.%qincludes the query string, including the leading?. If no query string exists, outputs an empty string.%vlogs theServerNameof the virtual host that served the request, so sites on the same server can be told apart. Requests that don’t match a named virtual host (for example, a request to the server’s bare IP) are logged under the default virtual host’sServerName. Give every<VirtualHost>its ownServerName: one without it logs the server’s global name, so several sites end up sharing one value.
Note
If you’re using this in a template or config management tool, the %{%Y-%m-%dT%H:%M:%S}t strftime pattern contains {% which many templating engines will try to interpret — escape it appropriately for your tooling.
Apply Custom Logging#
Once the format is defined, update the configuration file to apply it:
CustomLog /var/log/apache2/access.log json_combined
ErrorLog /var/log/apache2/error.log
Common Gotchas and Advanced Tweaks#
RewriteRule Placement (Extensionless URLs)#
If you’re using mod_rewrite to handle extensionless URLs (e.g. routing /status to /status.php), your rules must be placed inside a <Directory> block rather than directly at the global VirtualHost level.
At the global VirtualHost level, %{REQUEST_FILENAME} treats the target as a plain URI string instead of a filesystem path. This causes -f (file) and -d (directory) checks to always evaluate false:
<Directory /var/www/html>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)$ $1.php [L]
</Directory>
Proxy Configuration#
If Apache sits behind a proxy, remote_addr (%a) will log the proxy’s IP for every request unless you configure mod_remoteip first.
Enable the required modules: Run the following commands to enable both the proxy headers and rewrite engines:
a2enmod remoteip
a2enmod rewrite # if you need extensionless URL rewrites
Configure mod_remoteip: Create a dedicated configuration file at
/etc/apache2/conf-available/remoteip.conf:
# Replace 192.0.2.10 with your reverse proxy's IP address or CIDR range
RemoteIPHeader X-Real-IP
RemoteIPInternalProxy 192.0.2.10
Use RemoteIPInternalProxy when your proxy is on your own network. RemoteIPTrustedProxy won’t accept private client addresses (10/8, 172.16/12, 192.168/16, 127/8) from the header, so internal clients would be logged with the proxy’s IP instead of their own.
Activate the Configuration
a2enconf remoteip
systemctl restart apache2
Log the forwarding header: If Apache is behind a proxy, add a
forwarded_forfield to the LogFormat, betweenrefererandvhost:
\"forwarded_for\":\"%{X-Forwarded-For}i\",\
The full LogFormat for a proxied server:
LogFormat \
"{\"time\":\"%{%Y-%m-%dT%H:%M:%S}t\",\
\"remote_addr\":\"%a\",\
\"method\":\"%m\",\
\"uri\":\"%U\",\
\"query\":\"%q\",\
\"status\":%>s,\
\"bytes\":%B,\
\"duration\":%D,\
\"user_agent\":\"%{User-Agent}i\",\
\"referer\":\"%{Referer}i\",\
\"forwarded_for\":\"%{X-Forwarded-For}i\",\
\"vhost\":\"%v\"}" json_combined
forwarded_for records the proxy chain a request came through. Requests that reach Apache without going through the proxy log -, which lets the Apache kit spot clients bypassing the proxy. Only add this field if Apache is behind a proxy. On a server with no proxy every request logs -, and the bypass check would flag every client.
Note
Whoever sends the request sets this header, so a client can put anything in it. Treat it as context, not proof of where a request came from. remote_addr stays the reliable client address, because mod_remoteip only accepts it from your configured proxy.
Gravwell Configuration#
Gravwell Storage Well Configuration#
Setup the well configuration in your Gravwell indexers.
Sample well config:
Create or edit: /opt/gravwell/etc/gravwell.conf.d/apache-well.conf
[Storage-Well "apache"]
Location=/opt/gravwell/storage/apache
Tags=apache*
Gravwell Ingester Configuration: File Follower#
Sample Apache config:
Create or edit: /opt/gravwell/etc/file_follow.conf.d/apache.conf
[Follower "apache-access"]
Base-Directory = /var/log/apache2
File-Filter = access.log
Tag-Name = apache
[Follower "apache-error"]
Base-Directory = /var/log/apache2
File-Filter = error.log
Tag-Name = apache-err
Note
Remember to restart the service to apply the new config:
sudo systemctl restart gravwell_file_follow.service