# Internal network gateway

Expose services on a private network through one entry point, with public or self-managed certificates.

Put Ceryx in front of the services on a private network, such as an office, a homelab or a cloud VPC, so each one gets a hostname like `fakos.internal.withlogic.work` instead of an IP address and a port.

> [!NOTE] Example server address
> The examples use the subnet `10.0.0.0/24`: Ceryx runs at `10.0.0.5`, and the example service at `10.0.0.12`. Use your own subnet and the internal IP addresses of your Ceryx server and services in place of these examples.

## Route to services outside Docker

Targets don't have to be containers. Any HTTP address Ceryx can reach works:

```bash
docker compose -p ceryx exec ceryx curl -X POST localhost:8080/api/routes \
  -H 'Content-Type: application/json' \
  -d '{"source": "fakos.internal.withlogic.work", "target": "http://10.0.0.12:3000"}'
```

Ceryx resolves hostnames in targets with Docker's DNS, which also answers for names the server itself can resolve. If your internal names only resolve on another DNS server, set `CERYX_DNS_RESOLVER` to its IP address. Container names stop resolving when you do, so use IP addresses or internal hostnames for every target.

## Choose your certificates

### Public domains, reachable from the internet

If the hostnames are public and port 80 is reachable from the internet, Let's Encrypt works as usual. Nothing to set up.

### Internal-only hostnames

Let's Encrypt can't check hostnames it can't reach. Turn it off by setting this environment variable when running `docker compose up -d`:

```bash
CERYX_DISABLE_LETS_ENCRYPT=true
```

Then bring your own certificates, from your internal certificate authority, or a public wildcard certificate obtained with a DNS challenge. Either store them with the API:

```bash
jq -n \
  --rawfile certificate internal.pem \
  --rawfile private_key internal-key.pem \
  '{domain: "*.internal.withlogic.work", certificate: $certificate, private_key: $private_key}' |
docker compose -p ceryx exec -T ceryx curl -X POST localhost:8080/api/certificates \
  -H 'Content-Type: application/json' \
  -d @-
```

Or keep them as files. Mount them into the container with a `compose.override.yml` next to `compose.yml`:

```yaml
services:
  ceryx:
    volumes:
      - ./certs:/etc/ceryx/certs:ro
```

Then point the route at them:

```bash
docker compose -p ceryx exec ceryx curl -X PUT localhost:8080/api/routes/fakos.internal.withlogic.work \
  -H 'Content-Type: application/json' \
  -d '{"settings": {"certificate_path": "/etc/ceryx/certs/fakos.pem", "key_path": "/etc/ceryx/certs/fakos-key.pem"}}'
```

Ceryx reads certificate files when clients connect, so replacing the files takes effect without a restart. See [How certificates work](/docs/concepts/certificates) for which certificate wins when there are several.

## Listen on the internal network only

To keep Ceryx off other networks the server is on, bind its ports to the internal address by setting these environment variables when running `docker compose up -d`:

```bash
CERYX_EXTERNAL_HTTP_PORT=10.0.0.5:80
CERYX_EXTERNAL_HTTPS_PORT=10.0.0.5:443
```
