# The Hoody Proxy

**Page:** concepts/proxy

[Download Raw Markdown](./concepts/proxy.md)

---

# The Hoody Proxy

**Every URL in Hoody passes through one gateway.** Not a load balancer. Not a CDN. Not a reverse proxy in the traditional sense. The Hoody Proxy is the single point that transforms URL requests into container service calls, enforces security, terminates TLS, and preserves the real client IP -- all without a single line of configuration from you.

When you access a terminal, a desktop, a file, a database, or any service in any container -- you are going through the proxy. It is invisible, but it is the reason everything works.

One protocol. One gateway. One audit trail.

---

## How Routing Works

When a request arrives, the proxy does four things in microseconds:

### 1. Parse the URL

```
https://67e89abc123def456789abcd-890abcdef12345678901cdef-terminal-1.node-us.containers.hoody.icu/api/v1/terminal/execute
       └──────────┬──────────┘ └──────────┬──────────┘ └───┬───┘ └┘
              Project ID           Container ID        Service  Instance
```

The proxy extracts four identifiers from the hostname: project ID, container ID, service type, and instance number. No routing table. No configuration file. The URL IS the route.

### 2. Locate the Container

The proxy maintains a live map of all containers on the server. Given the container ID, it knows the container's internal IP, the ports each service listens on, and whether the container is running.

If the container does not exist or is stopped: `503 Service Unavailable`. No ambiguity.

### 3. Check Permissions

If the container or its project has [permissions configured](/concepts/security/), the proxy validates the request against every active authentication group: JWT claims, HTTP Basic credentials, client IP range, or bearer token. If no permissions are configured, the request passes -- the cryptographic URL is the authentication.

### 4. Dispatch to the Service

The proxy forwards the request to the correct internal service port inside the container. Terminal requests go to port 76. Display requests go to port 3998. SQLite goes to 5. (These internal Kit ports are operator-configurable defaults and never client-visible.) Your own HTTP servers go to whatever port you specified in the URL (`http-3000`, `http-5000`).

The client never sees internal ports. The client sees HTTPS on port 443. Always.

```
Client Request
    ↓
https://...-terminal-1.node-us.containers.hoody.icu
    ↓
Hoody Proxy (port 443)
  ├─ TLS termination
  ├─ URL parsing → project, container, service, instance
  ├─ Permission check (if configured)
  ├─ Real IP preservation (netfilter hooks)
  └─ Forward to container internal port 76
    ↓
Container's Terminal Service
    ↓
Response → Proxy → Client
```

---

## Wildcard TLS: Every URL Is HTTPS

The proxy terminates TLS for every request using wildcard certificates:

```
*.containers.hoody.icu
```

This means:

- **Every container service URL is HTTPS.** No exceptions. No HTTP fallback. No mixed-content warnings.
- **No certificate management.** You never generate, install, renew, or think about certificates.
- **Your URLs stay private.** Wildcard certificates do not appear in Certificate Transparency logs. Your specific container URLs are never published anywhere.
- **Custom domains get their own certificates.** Point a CNAME to a proxy alias, and Let's Encrypt issues a certificate automatically.


Standard Let's Encrypt certificates are publicly logged -- anyone can see what domains you host. Hoody's wildcard certificates mean your container URLs are invisible to the outside world. The URL is only known to people you share it with.


---

## Protocol Support

The proxy is not limited to basic HTTP request-response. It handles the full modern web stack:

### HTTP/1.1, HTTP/2, HTTP/3 (QUIC)

The proxy negotiates the best available protocol with each client automatically. HTTP/2 multiplexing eliminates head-of-line blocking. HTTP/3 over QUIC provides lower latency over unreliable networks.

You do nothing. The proxy handles negotiation, upgrade, and fallback.

### WebSocket

Terminal sessions, display streaming, and real-time services use WebSocket connections that upgrade from HTTP. The proxy handles WebSocket natively -- maintaining persistent bidirectional connections through the same URL, the same TLS certificate, the same authentication layer.

Multiple WebSocket connections to the same service URL create multiplayer sessions automatically. Two people open the same terminal URL? Two WebSocket connections, same terminal session. Multiplayer by architecture.

### What Is Not Supported

The proxy is HTTP-native. Non-HTTP protocols do not pass through it:

- UDP services (game servers, VoIP, custom UDP protocols) need direct access via [IPv4 addresses](/foundation/networking/ipv4/) or [SSH](/foundation/networking/ssh/)
- HTTP/3 (QUIC) is the exception -- it uses UDP but is fully supported because it is HTTP

---

## Real Client IP Preservation

This is the feature most proxies get wrong, and the one Hoody gets right at the kernel level.

**The problem with traditional proxies:** When traffic passes through a reverse proxy, the application sees the proxy's IP address, not the client's. The standard workaround is `X-Forwarded-For` headers -- but applications must explicitly parse them, firewalls cannot use them, and legacy code ignores them entirely.

**Hoody's solution:** Custom **netfilter hooks** in the host kernel rewrite connection metadata so that traffic arriving at the container appears to originate from the real client IP. The container's `remoteAddr` is the actual client address. No headers to parse. No application changes. No configuration.


  
    ```bash
    # Your application sees the real client IP automatically
    # No configuration needed

    # Verify with a simple Node.js server
    hoody terminal sessions exec \
      --command "node -e \"
        require('http').createServer((req, res) => {
          res.end('Your IP: ' + req.socket.remoteAddress);
        }).listen(3000);
      \"" \
      -c $CONTAINER_ID

    # Access via proxy -- shows REAL client IP, not proxy IP
    ```
  
  
    ```typescript
    // Any web framework sees real IPs -- no special configuration
    // Express
    app.get('/', (req, res) => {
      console.log(req.connection.remoteAddress);
      // "203.0.113.50" -- the actual client, not "10.0.0.1"
    });

    // Python Flask
    // request.remote_addr -> "203.0.113.50"

    // PHP
    // $_SERVER['REMOTE_ADDR'] -> "203.0.113.50"

    // Go
    // r.RemoteAddr -> "203.0.113.50:54321"
    ```
  
  
    ```bash
    # Standard iptables rules work with real client IPs
    # Inside the container:

    # Allow only your office IP
    iptables -A INPUT -s 203.0.113.0/24 -j ACCEPT

    # Block a known bad actor
    iptables -A INPUT -s 198.51.100.42 -j DROP

    # Works correctly because the proxy preserves real IPs
    # via kernel-level netfilter hooks
    ```
  


**Why this matters:** Every application, every language, every framework, every firewall rule, every legacy system -- all see the real client IP without modification. Access control, rate limiting, geo-routing, analytics -- all work as if the proxy does not exist.

---

## The Permission Model

The proxy is the single enforcement point for all access control. Not the individual services. Not the containers. Not the applications. The proxy.

### Open by Default

When you create a container, its URLs are accessible to anyone who has them. This sounds dangerous until you consider the math: container IDs are 24 hex characters, which means 2^96 possible combinations. Brute-forcing a container URL at one billion attempts per second would take longer than the age of the universe.

The URL itself is a cryptographic secret. Share it deliberately, and you have granted access. Keep it private, and it is private.

### Lock When Ready

When you need more than URL secrecy, the proxy supports layered authentication:

| Method | How It Works | Best For |
| :--- | :--- | :--- |
| **JWT** | Token with claims validation | API consumers, AI agents |
| **Password** | HTTP Basic Auth (username/password) | Quick protection, internal tools |
| **IP whitelist** | Allow specific IPs or CIDR ranges | Office access, known servers |
| **Bearer token** | Custom token in Authorization header | Service-to-service communication |

Permissions can be set at two levels:

- **Project level** -- applies to every container in the project
- **Container level** -- overrides project settings for specific containers

And permissions are granular per service:

```
Terminal: execute allowed, but files: read-only
Display: view allowed, but control denied
Database: query allowed, but modify denied
```


  
    ```bash
    # Set project-level proxy permissions
    hoody projects proxy permissions replace --project $PROJECT_ID \
      --groups office='{"type":"ip","range":"203.0.113.0/24"}' \
      --permissions office='{"terminal":true,"files":true,"display":true}' \
      --default deny
    ```
  
  
    ```typescript
    import { HoodyClient } from '@hoody-ai/hoody-sdk';

    const client = new HoodyClient({ baseURL: 'https://api.hoody.icu', token: process.env.HOODY_TOKEN });

    // Lock down a container for production
    await client.api.proxyPermissionsContainer.replace(containerId, {
      project: PROJECT_ID,
      container: containerId,
      groups: {
        office: { type: 'ip', range: '10.0.0.0/8' },
        ci: { type: 'token', value: 'secret-production-token' }
      },
      permissions: {
        office: { http: true, files: true },
        ci: { http: true, exec: true }
      },
      default: 'deny'
    });
    ```
  
  
    ```bash
    # Set container-level permissions
    curl -X PATCH "https://api.hoody.icu/api/v1/containers/$CONTAINER_ID/proxy/permissions" \
      -H "Authorization: Bearer $HOODY_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "project": "'$PROJECT_ID'",
        "container": "'$CONTAINER_ID'",
        "groups": {
          "office": { "type": "ip", "range": "203.0.113.0/24" },
          "ci": { "type": "token", "value": "ci-secret-token" }
        },
        "permissions": {
          "office": { "terminal": true, "display": true, "files": true },
          "ci": { "terminal": true, "exec": true, "files": true }
        },
        "default": "deny"
      }'
    ```
  


---

## Proxy Aliases

Cryptographic URLs are secure but unwieldy. Proxy aliases give containers human-friendly addresses:

```
https://my-api.node-us.containers.hoody.icu
```

An alias maps to a specific container and service. Multiple aliases can point to the same container. Aliases support custom domains via CNAME records with automatic SSL:

```
api.yourcompany.com  CNAME  my-api.node-us.containers.hoody.icu
```

The proxy handles the certificate. The proxy routes the request. The proxy enforces permissions. You update a DNS record and you are done.


  
    ```bash
    # Create an alias for your HTTP service
    hoody proxy create \
      --container-id $CONTAINER_ID \
      --alias my-api \
      --program http \
      --index 1
    ```
  
  
    ```typescript
    import { HoodyClient } from '@hoody-ai/hoody-sdk';

    const client = new HoodyClient({ baseURL: 'https://api.hoody.icu', token: process.env.HOODY_TOKEN });

    await client.api.proxyAliases.create({
      container_id: containerId,
      alias: 'my-api',
      program: 'http',
      index: 1
    });

    // https://my-api.node-us.containers.hoody.icu -> container's HTTP service
    ```
  
  
    ```bash
    # Create a proxy alias
    curl -X POST "https://api.hoody.icu/api/v1/proxy/aliases" \
      -H "Authorization: Bearer $HOODY_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "container_id": "890abcdef12345678901cdef",
        "alias": "my-api",
        "program": "http",
        "index": 1
      }'

    # Result: https://my-api.node-us.containers.hoody.icu
    # routes to your container's HTTP service
    ```
  


---

## Per-Server Architecture

Each bare metal server runs its own Hoody Proxy container. The proxy is not a centralized service -- it is local infrastructure on every server.

```
Server 1 (node-us)
  └─ Hoody Proxy Container
      └─ Routes to all containers on Server 1
         URLs: *.node-us.containers.hoody.icu

Server 2 (node-eu)
  └─ Hoody Proxy Container
      └─ Routes to all containers on Server 2
         URLs: *.node-eu.containers.hoody.icu
```

**Why per-server?**

- **Latency:** Proxy and containers are on the same machine. Routing adds less than 1ms.
- **Privacy:** Container traffic never leaves your server. The proxy runs on YOUR bare metal, not ours.
- **Reliability:** No centralized proxy failure. Each server is independent.
- **Locality:** The server name in the URL (`node-us`, `node-eu`) tells you which proxy handles it.

Cross-server communication happens via public URLs. Container on `node-us` calls a container on `node-eu` through `node-eu`'s proxy. Same protocol, same security, same pattern. Just URLs.

---

## The Single Security Enforcement Point

This is the architectural decision that simplifies everything: **all security decisions happen at the proxy.**

Not at the service level. Not in application code. Not in 13 different configuration files. At the proxy.

One place to:
- **Authenticate** -- every request, every service, every container
- **Authorize** -- per-service, per-group, per-container granularity
- **Encrypt** -- TLS termination for all traffic
- **Log** -- every request flows through one gateway
- **Rate limit** -- one enforcement point for all services
- **Observe** -- intercept and inspect any traffic via [hoody-exec](/kit/exec/)

When you audit your security posture, you audit the proxy configuration. When you lock down production, you lock down the proxy. When you open access for a demo, you adjust the proxy. One knob, one audit trail, one mental model.


The proxy enforces security, but it does not implement application-level security. If your web app has an SQL injection vulnerability, the proxy cannot fix that. The proxy secures the transport and access layer. Application security remains your responsibility.


---

## What the Proxy Enables

The proxy is not just infrastructure. It is the architectural decision that makes the rest of Hoody possible:

- **"Everything is a URL"** works because the proxy routes every URL to the right container and service.
- **Multiplayer** works because the proxy handles concurrent WebSocket connections to the same service.
- **Embeddability** works because the proxy serves every service over HTTPS, making them iframe-safe.
- **AI access** works because the proxy speaks HTTP -- the language AI already knows.
- **Custom domains** work because the proxy terminates TLS and issues certificates.
- **Security** works because the proxy is the single enforcement point.

Remove the proxy, and URLs stop working. Remove the proxy, and you need SSH, VNC, FTP, and a dozen other protocols. Remove the proxy, and Hoody is just another VM host.

The proxy is what makes containers into URLs. And URLs are what make Hoody, Hoody.

---

**Next:** [Security & Permissions](/concepts/security/) -- the full security model.