Skip to content

All about virtual hosts

A virtual host serves a domain (and optional aliases) over HTTP from a directory on disk. This article explains how to create one in Core and the rules that aren't obvious from the form fields.


Where to find virtual hosts

In Core, the top-level 'Virtual Hosts' page lists all virtual hosts on your clusters. Click 'Create' to add one.

Creating a virtual host

Domain and UNIX user

  • 'Domain' — the domain name to serve, e.g. dropflix.io.
  • 'UNIX User' — the UNIX user that owns the virtual host. The choice determines which FPM pools and Passenger apps you can attach below.

Language

After picking the UNIX user, choose the application 'Language' from the tile grid:

  • 'Static' — only serves files from disk. No dynamic backend. Always available.
  • 'PHP' — requests are handled by a PHP-FPM pool. The tile is only enabled if the selected UNIX user has at least one FPM pool. Picking it reveals an 'FPM Pool' dropdown, filtered to the user's pools.
  • 'NodeJS' — requests are handled by a Passenger app. The tile is only enabled if the selected UNIX user has at least one Passenger app. Picking it reveals a 'Passenger App' dropdown, filtered to the user's apps.

If a tile is greyed out, hover for the tooltip — it'll tell you what's missing (usually "no FPM pools available for this UNIX user" or "no Passenger apps available for this UNIX user").

Notice the framing: you pick a language, not a backend type. A virtual host can have an FPM pool or a Passenger app — never both. The language tile enforces this.

Domain uniqueness

The domain must be unique across all virtual hosts and URL redirects on the cluster — two things can't claim the same hostname. It also can't match the hostname of any node.

The same uniqueness rule applies to server aliases (which you add after creation): each alias is unique across all virtual hosts and URL redirects, and can't repeat the virtual host's own domain.

For every domain and every server alias, Core automatically creates a domain router — the routing object that decides which node a request lands on.

After creation

The virtual host page has separate update flows for:

  • 'Document Root' — the directory on disk that HTTP requests load files from.
  • 'Custom Config' — server-software config injected into the virtual host. See All about custom config snippets — the snippet syntax and ordering rules apply here too.
  • 'Directives' — for Apache virtual hosts, the .htaccess overrides you allow.
  • 'Language' — change between Static / PHP / NodeJS, or swap which FPM pool / Passenger app is attached.

Custom config caveats

On nginx virtual hosts, your custom config is placed in the server context.

If the virtual host has any basic authentication realms attached, the auth_basic and auth_basic_user_file directives are reserved. Don't set them in the default context of your custom config — basic auth manages them itself.

Behaviour worth knowing

Symlinks under the document root are only followed if the symlink target is owned by the same user as the document root. nginx serves a 403 otherwise, and Apache disallows it by default. This prevents one UNIX user's content from being exposed by a symlink they don't actually own.

Static-language virtual hosts won't serve .php

When a virtual host's language is 'Static' (no FPM pool attached), index.php is removed from the directory index automatically. This stops .php files from being served as downloads when no PHP backend is configured to execute them.

Atomic deploys typically work by pointing the document root at a symlink (e.g. /current) and swapping the symlink target to a new release directory. The cluster watches each virtual host's document root and, when the symlink's real path changes, reloads the attached FPM pool. The reload invalidates the PHP OPcache and realpath cache so the new release's code is picked up immediately.

The reload only fires for virtual hosts with an FPM pool attached — Static and NodeJS virtual hosts don't need one.

Doesn't fire when code lives outside the document root

The watcher only sees changes to the document root itself. If your code lives in a separate directory (e.g. document root /public includes code from /src) and only the /src symlink changes, the FPM pool won't be reloaded.

The simplest fix is to lay deploys out so the symlink swap happens at the document root.

Use cases by target group

Where virtual hosts land per group

  • Web agencies — one virtual host per client domain, each pointing at a different UNIX user. UNIX-user isolation means one client's leaked credentials, vulnerable plugin, or runaway script can't reach another client's files. Wrap pre-launch sites with basic auth or an IP allow-list on the domain router.
  • SaaS — a single virtual host for the marketing site, a second for the app, often a third for the admin. Pin sensitive admin domains to a firewall group so only the office can reach them.
  • Shops — keep the storefront virtual host separate from staging and from any internal tools (BI, admin). Cookie-based session leaks between subdomains are easy to introduce; one virtual host per concern keeps them clean.
  • Tech agencies / large platforms — atomic deploys via document-root symlink swaps are common. The automatic FPM pool reload above handles the OPcache invalidation so you don't have to.

Automatic CMS detection

The cluster periodically scans each virtual host's document root for known CMS markers (currently WordPress, detected by wp-includes/version.php). Hits are recorded against the virtual host so the management interface can show which CMS is running where.

Detection runs against the document root and its parent directory, so atomic-deploy layouts (document root is a release subdirectory, CMS sits at the parent) are picked up too.

Manually-created records are never removed

Auto-detected records disappear when the markers are gone from disk. CMS records you add manually are left alone — the cluster never deletes them, even if it can't find the corresponding files.