# How to Configure Custom Subdomain Routing with frp's vhost HTTP Support

> Configure custom subdomain routing with frp vhost HTTP support Easily set up subdomain routing by adjusting frp server and client configs and resolving DNS to your frps instance

- Repository: [fatedier/frp](https://github.com/fatedier/frp)
- Tags: how-to-guide
- Published: 2026-02-26

---

**To configure custom subdomain routing in frp, set the `subDomainHost` parameter in your server configuration and define the `subdomain` field in your client proxy settings, ensuring DNS resolves wildcard subdomains to your frps instance.**

The **fatedier/frp** repository enables virtual host (vhost) HTTP support, allowing a single frps server to route requests to different local services based on custom subdomains. By leveraging the `SubDomainHost` server option and the `subdomain` client parameter, you can expose services like `app.frps.com` and `api.frps.com` without allocating separate ports for each application.

## How frp vhost HTTP Routing Works

### The Role of SubDomainHost

In [`pkg/config/v1/server.go`](https://github.com/fatedier/frp/blob/main/pkg/config/v1/server.go), the `SubDomainHost` field specifies the base domain that frps appends to any client-requested subdomain. When this value is configured (for example, as `frps.com`), the server automatically accepts HTTP requests for any `*.frps.com` hostname. This setting is mandatory for custom subdomain functionality; without it, the server rejects subdomain registration attempts.

### Subdomain Validation Rules

Before accepting a proxy registration, frps validates the subdomain string according to strict rules defined in [`pkg/config/v1/validation/proxy.go`](https://github.com/fatedier/frp/blob/main/pkg/config/v1/validation/proxy.go). The subdomain must be non-empty, cannot contain dots (`.`) or asterisks (`*`), and requires that the server configuration includes a valid `SubDomainHost`. These constraints ensure valid DNS hostname formation and prevent wildcard conflicts during registration.

### Virtual Host Listener Registration

When an frpc client connects with a `subdomain` parameter, the server invokes `Muxer.Listen` in [`pkg/util/vhost/vhost.go`](https://github.com/fatedier/frp/blob/main/pkg/util/vhost/vhost.go) to register a vhost listener. The muxer stores this listener in an internal router keyed by the tuple `<domain, location, httpUser>`, where the domain is constructed as `<subdomain>.<SubDomainHost>`. This registration creates the mapping between the external hostname and the specific proxy connection.

### Request Routing and Dispatch

Incoming TCP connections on `vhostHTTPPort` are processed by `Muxer.handle` in [`pkg/util/vhost/vhost.go`](https://github.com/fatedier/frp/blob/main/pkg/util/vhost/vhost.go). The handler extracts the `Host` header from the HTTP request and calls `getListener` to retrieve the matching registered domain. The implementation supports wildcard matching patterns (such as `*.example.com`) and catch-all (`*`) logic, enabling flexible routing strategies based on the incoming Host header.

## Step-by-Step Configuration

### 1. Configure the frps Server

Enable vhost HTTP support by specifying `vhostHTTPPort` and define your base domain using `subDomainHost`.

```toml

# frps.toml

bindPort = 7000
vhostHTTPPort = 8080

# vhostHTTPSPort = 8443  # Optional: for HTTPS routing

subDomainHost = "frps.com"

```

The `vhostHTTPPort` directs frps to listen for HTTP-based virtual host routing on the specified port, while `subDomainHost` establishes the base domain for all custom subdomains.

### 2. Configure the frpc Client

Define an HTTP proxy type and specify the desired subdomain in your client configuration.

```toml

# frpc.toml

serverAddr = "your.frps.host"
serverPort = 7000

[[proxies]]
name = "web-service"
type = "http"
localPort = 80
subdomain = "app"  # Results in app.frps.com

```

When the client connects, frps registers a listener for `app.frps.com`. For HTTPS services, change `type` to `https` and ensure `vhostHTTPSPort` is enabled on the server.

### 3. Configure DNS Wildcard Records

Create a wildcard DNS `A` record pointing to your frps server IP address to route all subdomains to the tunnel endpoint.

```text
*.frps.com    A    203.0.113.10

```

This record ensures that requests to `app.frps.com`, `api.frps.com`, or any other subdomain resolve to your frps instance.

## Complete Configuration Example

### Server Configuration (frps.toml)

```toml
bindPort = 7000
vhostHTTPPort = 8080
vhostHTTPSPort = 8443
subDomainHost = "example.com"

# Optional authentication

auth.method = "token"
auth.token = "your-secret-token"

```

### Client Configuration (frpc.toml)

```toml
serverAddr = "203.0.113.10"
serverPort = 7000
auth.method = "token"
auth.token = "your-secret-token"

[[proxies]]
name = "blog"
type = "http"
localIP = "127.0.0.1"
localPort = 8080
subdomain = "blog"  # Accessible at blog.example.com

[[proxies]]
name = "api"
type = "https"
localIP = "127.0.0.1"
localPort = 8443
subdomain = "api"   # Accessible at api.example.com

```

### Request Flow

When a user accesses `http://blog.example.com/`, the request flows through the frps vhost muxer as follows:

1. DNS resolves `blog.example.com` to the frps server IP.
2. The browser sends an HTTP request with `Host: blog.example.com` to port 8080.
3. frps extracts the Host header and queries the muxer registry in [`pkg/util/vhost/vhost.go`](https://github.com/fatedier/frp/blob/main/pkg/util/vhost/vhost.go).
4. The muxer matches the request to the proxy registered with `subdomain = "blog"`.
5. frps forwards the TCP stream to the local service listening on `127.0.0.1:8080` via the established frpc connection.

## Summary

- **Enable vhost support** on frps by configuring `vhostHTTPPort` or `vhostHTTPSPort` and set `subDomainHost` to your base domain.
- **Define subdomains** in frpc proxy configurations using the `subdomain` parameter, which must adhere to validation rules (no dots or asterisks).
- **Configure DNS** with a wildcard record pointing to your frps server to handle all subdomain resolutions.
- **Routing mechanism** relies on the vhost muxer in [`pkg/util/vhost/vhost.go`](https://github.com/fatedier/frp/blob/main/pkg/util/vhost/vhost.go), which matches the HTTP Host header to registered listeners keyed by domain tuples.

## Frequently Asked Questions

### Can I use HTTPS with custom subdomain routing?

Yes. Enable `vhostHTTPSPort` in your [`frps.toml`](https://github.com/fatedier/frp/blob/main/frps.toml) server configuration and set `type = "https"` in your client proxy definition. The vhost muxer handles TLS connections on the dedicated HTTPS port and routes them based on the Server Name Indication (SNI) or Host header, following the same subdomain matching logic as HTTP.

### What happens if two clients register the same subdomain?

The frp server maintains a registry in `Muxer.Listen` (see [`pkg/util/vhost/vhost.go`](https://github.com/fatedier/frp/blob/main/pkg/util/vhost/vhost.go)) that keys listeners by the domain tuple. Attempting to register a duplicate subdomain typically results in an error or the newer registration overwriting the previous one, depending on the specific version and configuration. Each subdomain should be unique across all connected clients to ensure deterministic routing.

### Do I need to restart frps when adding new subdomains?

No. frp supports dynamic configuration for proxy registrations. When a new frpc client connects or an existing client updates its configuration, the server automatically registers or updates the vhost listener without requiring a restart of the frps process.

### Can I use nested subdomains like `app.test.frps.com`?

No. According to the validation logic in [`pkg/config/v1/validation/proxy.go`](https://github.com/fatedier/frp/blob/main/pkg/config/v1/validation/proxy.go), subdomain values cannot contain dots (`.`), which prevents nested subdomain registration. The subdomain must be a single DNS label (e.g., `app` or `test`), which is then concatenated with the `subDomainHost` base domain to form the complete hostname.