This site uses cookies for analytics. By continuing to browse this site, you agree to this use.
A JS Foundation Project

Basic server configuration for Apache

Basic server configuration for Apache

The following configurations should be a good starting point to pass most of webhint's checks that require adding to or modifying the server configuration.

There are some assumptions though:

# ######################################################################
# # MEDIA TYPES AND CHARACTER ENCODINGS (content-type) #
# ######################################################################

# ----------------------------------------------------------------------
# | Media types |
# ----------------------------------------------------------------------

# Serve resources with the proper media types (f.k.a. MIME types).
# https://webhint.io/docs/user-guide/hints/hint-content-type/

<IfModule mod_mime.c>

# Data interchange

# 2.2.x+

AddType text/xml xml

# 2.2.x - 2.4.x

AddType application/json json
AddType application/rss+xml rss

# 2.4.x+

AddType application/json map

# JavaScript

# 2.2.x+

# See: https://html.spec.whatwg.org/multipage/scripting.html#scriptingLanguages.
AddType text/javascript js mjs


# Manifest files

# 2.2.x+

AddType application/manifest+json webmanifest
AddType text/cache-manifest appcache


# Media files

# 2.2.x - 2.4.x

AddType audio/mp4 f4a f4b m4a
AddType audio/ogg oga ogg spx
AddType video/mp4 mp4 mp4v mpg4
AddType video/ogg ogv
AddType video/webm webm
AddType video/x-flv flv

# 2.2.x+

AddType image/svg+xml svgz
AddType image/x-icon cur

# 2.4.x+

AddType image/webp webp


# Web fonts

# 2.2.x - 2.4.x

AddType application/vnd.ms-fontobject eot

# 2.2.x+

AddType font/woff woff
AddType font/woff2 woff2
AddType font/ttf ttf
AddType font/collection ttc
AddType font/otf otf


# Other

# 2.2.x+

AddType text/vtt vtt

</IfModule>

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Serve all resources labeled as `text/html` or `text/plain`
# with the media type `charset` parameter set to `utf-8`.
#
# https://httpd.apache.org/docs/current/mod/core.html#adddefaultcharset

AddDefaultCharset utf-8

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Serve the following file types with the media type `charset`
# parameter set to `utf-8`.
#
# https://httpd.apache.org/docs/current/mod/mod_mime.html#addcharset

<IfModule mod_mime.c>
AddCharset utf-8 .appcache \
.atom \
.css \
.js \
.json \
.manifest \
.map \
.mjs \
.rdf \
.rss \
.vtt \
.webmanifest \
.xml
</IfModule>


# ######################################################################
# # Compression (http-compression) #
# ######################################################################

# Server resources compressed.
# https://webhint.io/docs/user-guide/hints/hint-http-compression/

# [!] The following relies on Apache being configured to have
# the correct filename extensions to media types mappings
# (see `content-type` section).
#
# [!] For Zopfli and Brotli this snippet assumes that running
# the build step will result in 3 version for every resource:
#
# * the original (e.g.: script.js) - this file should exists
# in case the user agent doesn’t requests things compressed
# * the file compressed with Zopfli (e.g.: script.js.gz)
# * the file compressed with Brotli (e.g.: script.js.br)

<IfModule mod_headers.c>
<IfModule mod_rewrite.c>

# Turn on the rewrite engine (this is necessary in order for
# the `RewriteRule` directives to work).
#
# https://httpd.apache.org/docs/current/mod/core.html#options

RewriteEngine On

# Enable the `FollowSymLinks` option if it isn't already.
#
# https://httpd.apache.org/docs/current/mod/core.html#options

Options +FollowSymlinks

# If the web host doesn't allow the `FollowSymlinks` option,
# it needs to be comment out or removed, and then the following
# uncomment, but be aware of the performance impact.
#
# https://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks

# Options +SymLinksIfOwnerMatch

# Depending on how the server is set up, you may also need to
# use the `RewriteOptions` directive to enable some options for
# the rewrite engine.
#
# https://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewriteoptions

# RewriteBase /

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# 1) Brotli

# If `Accept-Encoding` header contains `br`

RewriteCond "%{HTTP:Accept-encoding}" "br"

# and the request is made over HTTPS.

RewriteCond "%{HTTPS}" "on"

# The Brotli pre-compressed version of the file exists
# (e.g.: `script.js` is requested and `script.js.gz` exists).

RewriteCond "%{REQUEST_FILENAME}\.br" "-s"

# Then, serve the Brotli pre-compressed version of the file.

RewriteRule "^(.*)" "$1\.br" [QSA]

# Set the correct media type of the requested file. Otherwise,
# it will be served with the br media type since the file has
# the `.br` extension.
#
# Also, set the special purpose environment variables so
# that Apache doesn't recompress these files.

RewriteRule "\.(ico|cur)\.br$" "-" [T=image/x-icon,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.(md|markdown)\.br$" "-" [T=text/markdown,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.appcache\.br$" "-" [T=text/cache-manifest,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.atom\.br$" "-" [T=application/atom+xml,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.bmp\.br$" "-" [T=image/bmp,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.css\.br$" "-" [T=text/css,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.eot.\.br$" "-" [T=application/vnd.ms-fontobject,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.geojson\.br$" "-" [T=application/vnd.geo+json,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.html?\.br$" "-" [T=text/html,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.ics\.br$" "-" [T=text/calendar,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.json\.br$" "-" [T=application/json,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.jsonld\.br$" "-" [T=application/ld+json,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.m?js\.br$" "-" [T=text/javascript,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.otf\.br$" "-" [T=font/otf,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.rdf\.br$" "-" [T=application/rdf+xml,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.rss\.br$" "-" [T=application/rss+xml,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.svg\.br$" "-" [T=image/svg+xml,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.ttc\.br$" "-" [T=font/collection,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.ttf\.br$" "-" [T=font/ttf,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.txt\.br$" "-" [T=text/plain,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.vc(f|ard)\.br$" "-" [T=text/vcard,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.vtt\.br$" "-" [T=text/vtt,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.webmanifest\.br$" "-" [T=application/manifest+json,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.xhtml\.br$" "-" [T=application/xhtml+xml,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.xml\.br$" "-" [T=text/xml,E=no-brotli:1,E=no-gzip:1]

# Set the `Content-Encoding` header.

<FilesMatch "\.br$">
Header append Content-Encoding br
</FilesMatch>

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# 2) Zopfli

# If `Accept-Encoding` header contains `gzip` and the
# request is made over HTTP.

RewriteCond "%{HTTP:Accept-encoding}" "gzip"

# The Zopfli pre-compressed version of the file exists
# (e.g.: `script.js` is requested and `script.js.gz` exists).

RewriteCond "%{REQUEST_FILENAME}\.gz" "-s"

# Then serve the Zopfli pre-compressed version of the file.

RewriteRule "^(.*)" "$1\.gz" [QSA]

# Set the media types of the file, as otherwise, because
# the file has the `.gz` extension, it wil be served with
# the gzip media type.
#
# Also, set the special purpose environment variables so
# that Apache doesn't recompress these files.

RewriteRule "\.(ico|cur)\.gz$" "-" [T=image/x-icon,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.(md|markdown)\.gz$" "-" [T=text/markdown,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.appcache\.gz$" "-" [T=text/cache-manifest,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.atom\.gz$" "-" [T=application/atom+xml,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.bmp\.gz$" "-" [T=image/bmp,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.css\.gz$" "-" [T=text/css,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.eot.\.gz$" "-" [T=application/vnd.ms-fontobject,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.geojson\.gz$" "-" [T=application/vnd.geo+json,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.html?\.gz$" "-" [T=text/html,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.ics\.gz$" "-" [T=text/calendar,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.json\.gz$" "-" [T=application/json,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.jsonld\.gz$" "-" [T=application/ld+json,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.m?js\.gz$" "-" [T=text/javascript,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.otf\.gz$" "-" [T=font/otf,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.rdf\.gz$" "-" [T=application/rdf+xml,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.rss\.gz$" "-" [T=application/rss+xml,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.svg\.gz$" "-" [T=image/svg+xml,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.ttc\.gz$" "-" [T=font/collection,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.ttf\.gz$" "-" [T=font/ttf,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.txt\.gz$" "-" [T=text/plain,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.vc(f|ard)\.gz$" "-" [T=text/vcard,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.vtt\.gz$" "-" [T=text/vtt,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.webmanifest\.gz$" "-" [T=application/manifest+json,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.xhtml\.gz$" "-" [T=application/xhtml+xml,E=no-brotli:1,E=no-gzip:1]
RewriteRule "\.xml\.gz$" "-" [T=text/xml,E=no-brotli:1,E=no-gzip:1]

# Set the `Content-Encoding` header.

<FilesMatch "\.gz$">
Header append Content-Encoding gzip
</FilesMatch>

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Set the `Vary` header.

<FilesMatch "\.(br|gz)$">
Header append Vary Accept-Encoding
</FilesMatch>

</IfModule>
</IfModule>

<IfModule mod_deflate.c>

# 3) gzip
#
# [!] For Apache versions below version 2.3.7 you don't need to
# enable `mod_filter` and can remove the `<IfModule mod_filter.c>`
# and `</IfModule>` lines as `AddOutputFilterByType` is still in
# the core directives.
#
# https://httpd.apache.org/docs/current/mod/mod_filter.html#addoutputfilterbytype

<IfModule mod_filter.c>
AddOutputFilterByType DEFLATE "application/atom+xml" \
"application/json" \
"application/manifest+json" \
"application/rdf+xml" \
"application/rss+xml" \
"application/schema+json" \
"application/vnd.ms-fontobject" \
"application/xhtml+xml" \
"font/collection" \
"font/opentype" \
"font/otf" \
"font/ttf" \
"image/bmp" \
"image/svg+xml" \
"image/x-icon" \
"text/cache-manifest" \
"text/css" \
"text/html" \
"text/javascript" \
"text/plain" \
"text/vtt" \
"text/xml"
</IfModule>

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Special case: SVGZ
#
# If these files type would be served without the
# `Content-Enable: gzip` response header, user agents would
# not know that they first need to uncompress the response,
# and thus, wouldn't be able to understand the content.

<IfModule mod_mime.c>
AddEncoding gzip svgz
</IfModule>

</IfModule>


# ######################################################################
# # Caching (http-cache) #
# ######################################################################

# Serve resources with far-future expiration date.
# https://webhint.io/docs/user-guide/hints/hint-http-cache/

# [!] The following relies on Apache being configured to have
# the correct filename extensions to media types mappings
# (see apache `content-type` section).
#
# [!] Do not use or comment out the following if you are not
# using filename/path-based revving.

<IfModule mod_expires.c>

# Automatically add the `Cache-Control` header (as well as the
# equivalent `Expires` header).

ExpiresActive on

# By default, inform user agents to cache all resources for 1 year.

ExpiresDefault "access plus 1 year"


# Overwrite the previous for file types whose content usually changes
# very often, and thus, should not be cached for such a long period,
# or at all.

# AppCache manifest files

ExpiresByType text/cache-manifest "access plus 0 seconds"


# /favicon.ico (cannot be renamed!)

# [!] If you have access to the main Apache configuration
# file, you can match the root favicon exactly using the
# `<Location>` directive. The same cannot be done inside
# of a `.htaccess` file where only the `<Files>` directive
# can be used, reason why the best that can be done is match
# all files named `favicon.ico` (but that should work fine
# if filename/path-based revving is used)
#
# See also: https://httpd.apache.org/docs/current/sections.html#file-and-web.

<Files "favicon.ico">
ExpiresByType image/x-icon "access plus 1 hour"
</Files>


# Data interchange

ExpiresByType application/atom+xml "access plus 1 hour"
ExpiresByType application/rdf+xml "access plus 1 hour"
ExpiresByType application/rss+xml "access plus 1 hour"

ExpiresByType application/json "access plus 0 seconds"
ExpiresByType application/ld+json "access plus 0 seconds"
ExpiresByType application/schema+json "access plus 0 seconds"
ExpiresByType application/vnd.geo+json "access plus 0 seconds"
ExpiresByType text/xml "access plus 0 seconds"


# HTML

ExpiresByType text/html "access plus 0 seconds"


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Where needed add `immutable` value to the `Cache-Control` header

<IfModule mod_headers.c>

# Because `mod_headers` cannot match based on the content-type,
# the following workaround needs to be done.

# 1) Add the `immutable` value to the `Cache-Control` header
# to all resources.

Header merge Cache-Control immutable

# 2) Remove the value for all resources that shouldn't be have it.

<FilesMatch "\.(appcache|cur|geojson|ico|json(ld)?|x?html?|topojson|xml)$">
Header edit Cache-Control immutable ""
</FilesMatch>

</IfModule>

</IfModule>


# ######################################################################
# # DOCUMENT MODES (highest-available-document-mode) #
# ######################################################################

# Force Internet Explorer 8/9/10 to render pages in the highest mode
# available in the various cases when it may not.
#
# https://webhint.io/docs/user-guide/hints/hint-highest-available-document-mode/

<IfModule mod_headers.c>

# Because `mod_headers` cannot match based on the content-type,
# and the `X-UA-Compatible` response header should only be sent
# for HTML documents and not for the other resources, the following
# workaround needs to be done.

# 1) Add the header to all resources.

Header set X-UA-Compatible "IE=edge"

# 2) Remove the header for all resources that should not have it.

<FilesMatch "\.(appcache|atom|bbaw|bmp|crx|css|cur|eot|f4[abpv]|flv|geojson|gif|htc|ic[os]|jpe?g|m?js|json(ld)?|m4[av]|manifest|map|markdown|md|mp4|oex|og[agv]|opus|otf|pdf|png|rdf|rss|safariextz|svgz?|swf|topojson|tt[cf]|txt|vcard|vcf|vtt|webapp|web[mp]|webmanifest|woff2?|xloc|xml|xpi)$">
Header unset X-UA-Compatible
</FilesMatch>

</IfModule>

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# If the `X-UA-Compatible` header is not needed, remove or comment
# out the section above. If it's still added from somewhere in the
# stack (e.g. the framework level, language level such as PHP, etc.),
# and that cannot be changed, the following may be used to remove it
# at the Apache level.

# <IfModule mod_headers.c>
# Header unset X-UA-Compatible
# </IfModule>


# ######################################################################
# # SECURITY #
# ######################################################################

# ----------------------------------------------------------------------
# | HTTP Strict Transport Security (strict-transport-security) |
# ----------------------------------------------------------------------

# Serve resources with the Strict-Transport-Security header.
# https://webhint.io/docs/user-guide/hints/hint-strict-transport-security/
#
# [!] Uncomment the following if the site supports HTTPS.

# <IfModule mod_headers.c>
# Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# </IfModule>


# ######################################################################
# # Unnedded / Disallowed headers #
# ######################################################################

# ----------------------------------------------------------------------
# | HTML only headers (no-html-only-headers) |
# ----------------------------------------------------------------------

# Do not send HTML only headers for non-HTML resources,
# https://webhint.io/docs/user-guide/hints/hint-no-html-only-headers/#page-heading

<IfModule mod_headers.c>

# Because `mod_headers` cannot match based on the content-type,
# the following workaround needs to be used.

<FilesMatch "\.(appcache|atom|bbaw|bmp|crx|css|cur|eot|f4[abpv]|flv|geojson|gif|htc|ic[os]|jpe?g|m?js|json(ld)?|m4[av]|manifest|map|markdown|md|mp4|oex|og[agv]|opus|otf|pdf|png|rdf|rss|safariextz|svgz?|swf|topojson|tt[cf]|txt|vcard|vcf|vtt|webapp|web[mp]|webmanifest|woff2?|xloc|xml|xpi)$">
Header unset Content-Security-Policy
Header unset X-Content-Security-Policy
Header unset X-Frame-Options
Header unset X-UA-Compatible
Header unset X-WebKit-CSP
Header unset X-XSS-Protection
</FilesMatch>
</IfModule>

# ----------------------------------------------------------------------
# | Disallowed headers (no-disallowed-headers) |
# ----------------------------------------------------------------------

# Remove unneeded headers.
# https://webhint.io/docs/user-guide/hints/hint-no-disallowed-headers/

# If the headers are sent, in most cases, to make Apache stop sending
# them requires removing the configurations that tells Apache to add
# them (e.g. for the `X-UA-Compatible` header, that would be mean
# removing something such as `Header set X-UA-Compatible "IE=edge"`).
# However, if the headers are added from somewhere in the stack (e.g.:
# the framework level, language level such as PHP, etc.), and that
# cannot be changed, you can try to remove them at the Apache level,
# using the following.

<IfModule mod_headers.c>
Header unset P3P
Header unset Public-Key-Pins
Header unset Public-Key-Pins-Report-Only
Header unset X-AspNet-Version
Header unset X-AspNetMvc-version
Header unset X-Powered-By`
Header unset X-Runtime
Header unset X-Version
</IfModule>

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Prevent Apache from sending in the `Server` response header its
# exact version number, the description of the generic OS-type or
# information about its compiled-in modules.
#
# https://httpd.apache.org/docs/current/mod/core.html#servertokens

# [!] The following will only work in the main Apache configuration
# file, so do not uncomment the following if this is include it
# in a .htaccess file!

# ServerTokens Prod


# ######################################################################
# # Custom configurations #
# ######################################################################

# Add here your custom configurations.