# Global configuration; see https://securityheaders.com Header set X-Frame-Options "sameorigin" Header set X-Content-Type-Options "nosniff" Header set X-Xss-Protection "1; mode=block" Header set Referrer-Policy "no-referrer-when-downgrade" ServerTokens prod ServerSignature off AddDefaultCharset utf-8 UseCanonicalName on LogLevel warn AddType image/avif .avif AddType font/woff2 .woff2 AddOutputFilterByType deflate image/svg+xml AddOutputFilterByType deflate application/xhtml+xml AddOutputFilterByType deflate image/vnd.microsoft.icon image/x-icon TraceEnable off Protocols h2 h2c http/1.1 # Set up caching directives for infrequently changed files ExpiresActive on ExpiresByType application/javascript "access plus 6 months" ExpiresByType text/javascript "access plus 6 months" ExpiresByType font/woff2 "access plus 6 months" ExpiresByType image/avif "access plus 6 months" ExpiresByType image/gif "access plus 6 months" ExpiresByType image/jpeg "access plus 6 months" ExpiresByType image/png "access plus 6 months" ExpiresByType image/svg+xml "access plus 6 months" ExpiresByType image/vnd.microsoft.icon "access plus 6 months" ExpiresByType image/x-icon "access plus 6 months" ExpiresByType text/css "access plus 6 months" # These lines are a workaround for an Apache bug that prevents mod_deflate, etags, and ExpiresByType working at the same time. # This is probably still broken in 18.04. See https://stackoverflow.com/questions/896974/apache-is-not-sending-304-response-if-mod-deflate-and-addoutputfilterbytype-is # Can possibly be fixed in Ubuntu 22.04 using https://httpd.apache.org/docs/trunk/mod/mod_deflate.html#deflatealteretag FileETag All RequestHeader edit "If-None-Match" "^\"(.*)-gzip\"$" "\"$1\"" Header edit "ETag" "^\"(.*[^g][^z][^i][^p])\"$" "\"$1-gzip\"" # SSL hardening; see https://mozilla.github.io/server-side-tls/ssl-config-generator/ SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256 SSLHonorCipherOrder on SSLCompression off SSLSessionTickets off SSLStaplingCache shmcb:/var/run/ocsp(128000) # SSL Stapling should be off for testing to prevent errors in log files, and on for live SSLUseStapling off SSLStaplingResponderTimeout 5 SSLStaplingReturnResponderErrors off Define domain standardebooks.test Define web_root /standardebooks.org/web Define conf_rewrite_root ${web_root}/config/apache/rewrites ServerName ${domain} ServerAlias www.${domain} RedirectPermanent / https://${domain}/ ServerName ${domain} ServerAlias www.${domain} DocumentRoot ${web_root}/www ErrorDocument 404 /404 ErrorDocument 451 /451 ErrorLog /var/log/local/www-error.log DirectorySlash Off RewriteEngine on SSLEngine on SSLCertificateFile ${web_root}/config/ssl/${domain}.crt SSLCertificateKeyFile ${web_root}/config/ssl/${domain}.key Header always set Strict-Transport-Security "max-age=15768000" Header set Content-Security-Policy "default-src 'self';" # Disable .htaccess files AllowOverride none # Disable unneeded options Options none # Allow access to www/ Require all granted # Pass HTTP Authorization headers to PHP-FPM CGIPassAuth on AddType application/x-mobi8-ebook .azw3 # Serve distributables using the "download" dialog instead of opening in-browser # Note: the trailing e in the Header directive is required # In modern browsers this is handled with the a@download attribute, we keep this here for compatibility SetEnvIf Request_URI ^/ebooks/.+?/downloads/(.+)$ FILENAME=$1 Header set Content-Disposition "attachment; filename=%{FILENAME}e" # We explicitly set the content-type for items in the /vocab/ directory, because Apache doesn't set it for us, # and we need a content-type header when using the "nosniff" header. See https://bugzilla.mozilla.org/show_bug.cgi?id=1547076 Header set Content-Type "text/plain" # Enable HTTP CORS so that browser-based readers like Readium can access opds and ebooks # Allow fonts for newsletter emails # See https://github.com/standardebooks/tools/issues/2 Header set Access-Control-Allow-Origin "*" # We use a different CSP policy for single-page files because our default one doesn't allow inline images or CSS Header set Content-Security-Policy "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline';" # Remove www from requests RewriteCond %{HTTP_HOST} ^www\.(.+) [NC] RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L] # PHP-FPM configuration # See https://serverfault.com/questions/450628/apache-2-4-php-fpm-proxypassmatch/510784 # Required for FPM to receive POST data sent with Transfer-Encoding: chunked SetEnv proxy-sendcl 1 # Forward all PHP requests to the php-fpm pool for this domain. SetHandler "proxy:unix:///run/php/${domain}.sock|fcgi://${domain}" Header set Cache-Control "no-store" # Set some proxy properties. ProxySet connectiontimeout=5 timeout=240 # In RewriteCond, RewriteRule gets evaluated BEFORE RewriteCond, so $1 refers to the first # match in RewriteRule # Rewrite POST /some/url -> POST /some/url/post.php RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^(post|delete|put)$/" RewriteCond %{DOCUMENT_ROOT}/$1/%1.php -f RewriteRule ^([^\.]+)$ $1/%1.php [L] # In case of 404, serve the 404 page specified by ErrorDocument, not the default FPM error page. # Note that we can't use `ProxyErrorOverride on` because that catches ALL 4xx and 5xx HTTP headers # and serves the default Apache page for them. RewriteCond %{REQUEST_FILENAME} \.php$ RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI} !-f RewriteRule (.*) - [H=text/html] # Received: /filename.php and /filename.php exists in filesystem; Result: 301 redirect to /filename and restart request RewriteCond %{REQUEST_FILENAME} \.php$ RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI} -f RewriteRule ^/(.+)\.php$ /$1 [R=301,L] # Received: /filename and /filename.php exists in filesystem; Result: change /filename to /filename.php and continue processing RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI} !-f RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI} !-d RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI}.php -f RewriteRule ^(.+)$ $1.php [QSA] # End PHP-FPM configuration # Received: /filename and /filename.xml exists in filesystem; Result: rewrite to /filename.xml RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}.xml -f RewriteRule (.*) $1.xml # Remove trailing slashes RewriteRule ^/(.+?)/$ /$1 [R=301,L] # Redirect ToC of XHTML representation of books RewriteRule ^/ebooks/(.+?)/text$ /ebooks/$1/toc.xhtml [L] # Received: /filename and /filename.xhtml exists in filesystem; Result: rewrite to /filename.xhtml RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}.xhtml -f RewriteRule (.*) $1.xhtml # Redirect index pages RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}/index.php -f RewriteRule (.*) $1/index.php RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}/index.xml -f RewriteRule (.*) $1/index.xml # Remove newline characters inserted by accident in some email clients RewriteRule ^(.*)\r\n[\ ]?(.*)$ $1$2 [R=301,N] RewriteRule ^(.*)/r/n[\ ]?(.*)$ $1$2 [R=301,N] RewriteRule ^(.*)/[rn]$ $1 [R=301,N] Include ${conf_rewrite_root}/misc.conf Include ${conf_rewrite_root}/feeds.conf Include ${conf_rewrite_root}/ebooks.conf Include ${conf_rewrite_root}/newsletters.conf Include ${conf_rewrite_root}/artworks.conf Include ${conf_rewrite_root}/polls.conf Include ${conf_rewrite_root}/users.conf # Specific config for /ebooks///downloads # Both directives are required XSendFile on XSendFilePath ${web_root}/www/ebooks # Specific config for /bulk-downloads # Both directives are required XSendFile on XSendFilePath ${web_root}/www/bulk-downloads # Specific config for /feeds # This must be defined at the top level /feeds/ directory # Both directives are required XSendFile on XSendFilePath ${web_root}/www/feeds