RewriteEngine on # ----- Force HTTPS everywhere ----- # Redirect any plain-http request to https. The two conditions together avoid # a redirect loop when TLS is terminated by an upstream proxy (which forwards # X-Forwarded-Proto: https while %{HTTPS} may read as off). RewriteCond %{HTTPS} !=on RewriteCond %{HTTP:X-Forwarded-Proto} !=https RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] # ----- Language root redirect ----- # Send the bare root to the default-language homepage, on WHATEVER host is # serving the site (relative — no cross-domain redirect). Do NOT force # msosorg.com here: the site is currently served from msos.spletnimojster.si, # and msosorg.com does not yet serve these files, so forcing it 404s. # Once msosorg.com is pointed at this same docroot, a canonical-host redirect # can be added back safely. RewriteRule ^/?$ /en/ [R=301,L] # ----- Defense-in-depth: block dev/sensitive files if they ever ship ----- # (The deploy already excludes these; this is a safety net. robots.txt, # sitemap.xml and site.webmanifest are intentionally NOT blocked.) Require all denied Order allow,deny Deny from all # Block internal folders and VCS/CI dirs. RewriteRule (^|/)\.(git|gitea)(/|$) - [F,L] RewriteRule ^(docs|templates|tools)(/|$) - [F,L] # ============================================================ # Static-site defaults (added for SEO / performance / 404). # Wrapped in guards so a missing module can't 500. # Ignored by nginx (harmless). No extra HTTPS/host redirects — # the host handles TLS and the rule above handles the root. # ============================================================ # ----- Custom branded error page ----- ErrorDocument 404 /404.html # ----- Don't expose directory listings ----- Options -Indexes # ----- Correct MIME types (WebP, SVG, manifest, fonts) ----- AddType image/webp .webp AddType image/svg+xml .svg AddType application/manifest+json .webmanifest AddType image/x-icon .ico AddType font/woff2 .woff2 # ----- Gzip / compression for text assets ----- AddOutputFilterByType DEFLATE text/html text/css text/plain text/xml AddOutputFilterByType DEFLATE application/javascript application/json AddOutputFilterByType DEFLATE application/xml application/manifest+json AddOutputFilterByType DEFLATE image/svg+xml # ----- Browser caching ----- # No file-hashing on this site, so keep CSS/JS short (updates must propagate) # and cache images/fonts longer. HTML is always revalidated. ExpiresActive On ExpiresByType text/html "access plus 0 seconds" ExpiresByType text/css "access plus 1 day" ExpiresByType application/javascript "access plus 1 day" ExpiresByType image/webp "access plus 1 month" ExpiresByType image/jpeg "access plus 1 month" ExpiresByType image/png "access plus 1 month" ExpiresByType image/svg+xml "access plus 1 month" ExpiresByType image/x-icon "access plus 1 month" ExpiresByType font/woff2 "access plus 1 month" Header set X-Content-Type-Options "nosniff" # HSTS: after the first HTTPS visit, browsers refuse plain HTTP for this # host. Conservative 6-month max-age, no preload/includeSubDomains so it # stays easy to adjust. (Ignored by browsers over plain HTTP — harmless.) Header always set Strict-Transport-Security "max-age=15768000" # Clickjacking protection — the site should never be framed by other origins. Header always set X-Frame-Options "SAMEORIGIN" # Don't leak full page URLs to third parties (analytics, external links). Header always set Referrer-Policy "strict-origin-when-cross-origin" # Explicitly disable browser features the site doesn't use. Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" # Content-Security-Policy (ENFORCING). Allowlist verified in a browser # against a server sending this exact header — every real resource passes: # self assets, Google Fonts, Font Awesome (cdnjs), gtag, GA beacons, the # Google Forms endpoint (membership + guide feedback), the MailerLite JSONP # newsletter, and YouTube (nocookie) embeds. 'unsafe-inline' is required for # the in- consent/analytics bootstrap and a few inline handlers; the # host allowlists still constrain where scripts load from and where data can # be sent (connect/form-action/base-uri/object-src), which is the real value # here on top of the site's existing output escaping. Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://assets.mailerlite.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdnjs.cloudflare.com; font-src 'self' https://fonts.gstatic.com https://cdnjs.cloudflare.com data:; img-src 'self' data: https:; connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com https://analytics.google.com https://www.googletagmanager.com https://docs.google.com https://assets.mailerlite.com; frame-src https://www.youtube-nocookie.com https://www.youtube.com; frame-ancestors 'self'; base-uri 'self'; form-action 'self' https://docs.google.com; object-src 'none'" Header set Cache-Control "no-cache, must-revalidate"