WeynWebWorks HTTP-Server

All websites on our shared hosting platform are served by our own HTTP server. This article explains how your site is served and which special functions and configuration options are available to you.

Most options are configured in a file called .htconfig.yaml in the root folder of your website. Don’t hesitate to contact us if you want any of these options changed — we’re happy to help.

How your files are served

The server matches the requested domain name to a folder on the server. For example, requests for www.example.com are served from that domain’s own folder. Multiple domain names can point to the same website. If no folder matches, a default site is used when one is configured.

When a .htconfig.yaml file exists in your site’s root folder, it controls the behaviour described below.

Choosing a preset

The preset key sets the default behaviour of your website:

  • wordpress
  • drupal7
  • shopware6
  • craftcms
  • SPA (single-page application)
  • static

For wordpress, drupal7, shopware6 and craftcms, index.php is served when there is no immediate file match — this is what makes “pretty permalinks” work.

The wordpress preset has extra security optimisations:

  • xmlrpc.php is not served (error 401) unless you explicitly enable it with xmlrpc: true.
  • PHP execution is blocked inside wp-content/uploads and wp-content/cache — the directories where plugins (and attackers) can write files. A PHP webshell uploaded there can never run.

If a legitimate plugin needs PHP execution inside one of those directories, or you want to block additional directories, this can be fine-tuned per site with allow/deny lists (allow rules always win):

php:
  exec_paths:
    allow: ["wp-content/cache/my-plugin"]   # carve-out: execution allowed here again
    deny:  ["wp-content/uploads/private"]   # extra blocked directory

This only affects direct web requests to PHP files; PHP’s own internal file includes keep working everywhere.

For a single-page application, the SPA preset serves index.html as the fallback for unknown paths, so client-side routing works. The fallback file can be changed, e.g. for an SPA bootstrapped by PHP:

preset: SPA
spa:
  fallback: index.php

The static preset is meant for plain static sites (for example Astro or Hugo output). Requests resolve in this order: an exact file match, then index.html inside the requested directory, then a custom 404.html in the site root if you created one. Sensible caching headers are added automatically: HTML pages are always revalidated so your updates are visible immediately, content-hashed assets (such as Astro’s /_astro/ files) are cached for a year, and other files (images, fonts, …) for one hour.

Redirects

You can define redirects in .htconfig.yaml, matched by regular expression, path prefix, or exact path. The first matching rule (from top to bottom) wins:

redirects:
- regex: ^/old-blog/(.*)$
  target: /blog/$1
- prefix: /shop
  target: https://shop.example.com
- fullpath: /old-page
  target: /new-page

Choosing your PHP version

Your site can be pinned to a specific PHP version:

php:
  version: 8.3

Available versions are mapped server-side; let us know if you want to switch.

Restoring the visitor’s real IP address

Because all traffic passes through our proxy, PHP’s $_SERVER["REMOTE_ADDR"] would normally show the proxy’s address instead of your visitor’s. Setting the following in .htconfig.yaml restores the real address:

remote_address: restore_to_forwarded_for

Image optimization

Resizing images via the URL

Images can be resized on the fly by adding a special prefix to the request path. For example:

https://www.example.com/!resize:cover:540x400/uploads/photo.jpg

This serves uploads/photo.jpg resized to 540×400 pixels. Available strategies:

  • cover: keeps the aspect ratio and fills the whole space; excess pixels are cropped (centered).
  • fit: keeps the aspect ratio and fits within the space; empty space is filled with black.

Modern formats (WebP, AVIF)

You can also request a modern image format directly via the URL:

  • /!format:webp/uploads/photo.jpg — serve as WebP (default quality 82)
  • /!format:webp:75/uploads/photo.jpg — WebP at quality 75
  • /!format:avif/uploads/photo.jpg — AVIF (default quality 60)
  • /!original/uploads/photo.jpg — force the untouched original

These compose with resizing, e.g. /!resize:cover:540x400/!format:webp/uploads/photo.jpg.

Automatic image optimization

On request, we can enable automatic optimization per site. Two complementary modes exist, both opt-in:

  • HTML rewriting: your pages’ <img> tags are automatically rewritten to offer WebP/AVIF variants (via a <picture> element or a src swap based on the browser’s capabilities). Images are converted once and cached; your visitors automatically get the smallest file their browser supports.
  • Direct-request override: plain requests for .jpg/.png files are answered with the optimized WebP/AVIF variant directly.

Converted images are cached on the server, so the conversion cost is paid only once per image. Contact us if you want this enabled for your site.

Page cache

For WordPress sites we can enable a full-page cache: pages for anonymous visitors are served directly from the server’s memory instead of running PHP on every request. This makes your site noticeably faster and reduces load — especially during traffic spikes.

  • Only anonymous traffic is cached. As soon as a visitor is logged in, has something in their cart, or has commented (detected via cookies), their requests bypass the cache and are rendered fresh.
  • Admin and API paths (/wp-admin/, /wp-login.php, /wp-json/, …) are never cached.
  • You can see the cache in action via the X-Cache response header: HIT means served from memory, MISS means freshly rendered by PHP.
  • Cached pages expire automatically. By default a page stays cached for 60 seconds (plus a small random margin, so many pages don’t all expire at the same moment). A hard upper limit of 10 minutes guarantees no visitor ever sees a page that is more than 10 minutes stale. Both can be tuned per site via default_ttl_seconds and max_ttl_seconds. The cache can also be purged automatically when you publish or update content.

The page cache is opt-in per site — contact us to have it enabled.

Rate limiting

To protect your site against brute-force attacks and abusive bots, we can apply rate limits per visitor IP address to specific paths — for example limiting requests to /wp-login.php or your API endpoints. Visitors who exceed the limit are first delayed, and only receive an error (HTTP 429, “too many requests”) if they persist. Normal visitors never notice this.

Contact forms without PHP

Static sites can have fully working contact forms handled by the server itself — no PHP or external form service needed. Your HTML form simply posts to /_form/{name} on your own domain. Submissions are validated, stored, and e-mailed to you.

Features:

  • Field validation (required fields, maximum lengths, e-mail format).
  • Spam protection via a hidden “honeypot” field and optional Cloudflare Turnstile or hCaptcha.
  • Per-visitor submission rate limiting.
  • Custom e-mail subject and body templates, and custom success/error pages to redirect to.

Ask us to set up a form for your site.

Pre-compressed static assets

If your build process generates pre-compressed files (for example style.css.br or style.css.gz next to style.css), the server serves them directly to browsers that support Brotli or gzip — with the correct content type. This gives you maximum compression with zero runtime cost.

Further things to note

  • GET requests to wp-login.php are cached in server memory as an optimisation.
  • Behind the scenes the server also protects all sites with automatic abuse detection: clients that misbehave (aggressive scraping, attacking common vulnerability paths) are rate-limited or blocked before they ever reach your site.

See also: