# Openship Edge Routing, OpenResty Domain, and TLS Management: Architecture and Configuration

> Discover Openship edge routing with OpenResty domain and TLS management. Learn how OpenResty and Lua handle traffic routing and automatic TLS termination via generated NGINX configurations.

- Repository: [oblien/openship](https://github.com/oblien/openship)
- Tags: architecture
- Published: 2026-08-19

---

**Openship implements a fully-featured edge layer using OpenResty (NGINX + Lua) that handles traffic routing, domain management, and automatic TLS termination through generated NGINX configuration fragments kept in sync with TypeScript constants.**

Openship's edge routing, OpenResty domain, and TLS management stack is built on a unified OpenResty layer that runs either containerized via Docker Compose or as a bare-metal process installed by the control plane. The entire edge configuration is derived from shared TypeScript constants to guarantee identical behavior across deployment modes. Understanding these internals is essential for operators who need to debug vhosts, customize proxy behavior, or secure custom domains with Let's Encrypt.

## Core Edge Components and Source Files

The edge architecture is defined by a set of TypeScript generators and constants that emit NGINX configuration fragments. These modules ensure that every deployed edge—whether inside a container or on a bare-metal box—uses the exact same directives.

### Baked [`nginx.conf`](https://github.com/oblien/openship/blob/main/nginx.conf) and Configuration Drift Prevention

In [`packages/adapters/src/infra/edge-baked-conf.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/infra/edge-baked-conf.ts), the **`bakedEdgeNginxConf`** generator assembles a complete [`nginx.conf`](https://github.com/oblien/openship/blob/main/nginx.conf) by pulling values from shared constants including **`EDGE_SHARED_DICTS`** and **`EDGE_CLIENT_MAX_BODY_SIZE`**. The container image ships with a pre-generated copy of this file, and a CI test asserts that the generator output matches the baked copy exactly. This mechanism prevents drift between the containerized edge and the bare-metal edge.

### Real-IP Handling for Cloudflare and Reverse Proxies

The [`packages/adapters/src/infra/edge-real-ip.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/infra/edge-real-ip.ts) module constructs the `real_ip` configuration block. By default, OpenResty trusts Cloudflare's CIDR ranges and reads the client address from the **`CF-Connecting-IP`** header. Operators can override the header name via **`OPENSHIP_EDGE_REAL_IP_HEADER`** and append additional trusted proxies. This preserves accurate logging and rate-limiting regardless of the upstream proxy topology.

### Unrouted Host Fallback Responses

When a request arrives for a hostname with no deployed application, the edge returns a minimal HTML page defined in [`packages/adapters/src/infra/edge-not-found.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/infra/edge-not-found.ts). The constant **`EDGE_NOT_FOUND_HTML`** contains the sentinel string **`openship-edge-unrouted`**, and the content is embedded directly in the default NGINX `location /` block via **`EDGE_NOT_FOUND_LOCATION`**. This fallback works identically in both container and bare-metal deployments.

### ACME HTTP-01 Challenges and Default TLS Certificates

TLS management is coordinated through constants defined in [`packages/adapters/src/infra/openresty-lua.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/infra/openresty-lua.ts). The default HTTP server listens on port 80 and includes a location that proxies ACME HTTP-01 challenges to Certbot's temporary listener. Meanwhile, the HTTPS catch-all block—**`edgeHttpsDefaultBlock`**—loads a placeholder self-signed certificate generated at build time, whose path is returned by **`edgeDefaultCertPaths`**. Once a real domain is added, the control plane swaps in the Let's Encrypt certificate and reloads the edge.

### Shared Lua Memory Zones

OpenResty allocates shared dictionaries for analytics, live-log streaming, and rate-limit counters. The sizes of these `lua_shared_dict` zones are centralized in **`EDGE_SHARED_DICTS`** and emitted by the baked configuration generator in [`packages/adapters/src/infra/edge-baked-conf.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/infra/edge-baked-conf.ts). Keeping these sizes identical across container and bare-metal edges prevents out-of-memory mismatches under load.

## Domain Onboarding Flow from Registration to HTTPS

Adding a custom domain to Openship triggers a deterministic sequence:

1. The user adds the domain through the dashboard or CLI.
2. The control plane writes a new vhost file (`<slug>.conf`) into the shared `sites-enabled` directory. This file contains a `server_name` directive and includes the real-IP block generated by `edgeRealIpConf`.
3. The control plane triggers a configuration reload via `docker exec … nginx -s reload` for containerized edges or `nginx -s reload` for bare-metal boxes.
4. OpenResty picks up the new config and begins serving traffic for the domain.
5. If TLS is enabled, the edge initiates the ACME HTTP-01 flow, places the resulting certificate at the path defined by `edgeDefaultCertPaths`, and updates the vhost.

On renewal or removal, [`apps/api/src/lib/edge-vhost-repair.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/lib/edge-vhost-repair.ts) rewrites vhost files on the fly to keep the edge consistent.

## Edge CLI Commands for Operations and Import

The CLI entry point at [`apps/cli/src/commands/edge.ts`](https://github.com/oblien/openship/blob/main/apps/cli/src/commands/edge.ts) exposes several commands for edge management:

| Command | Purpose | Example |
|---|---|---|
| `openship edge preflight` | Validates that the host can run the OpenResty edge by checking Docker availability, port availability, and required environment variables. | `openship edge preflight` |
| `openship edge import` | Imports existing NGINX or OpenResty configurations into the Openship control plane and converts them to the internal format. | `openship edge import /etc/nginx/sites-enabled` |
| `openship up --public-url https://example.com` | Starts a self-hosted Openship instance with the edge listening on `:80` and `:443`, automatically handling TLS for the specified domain. | `openship up --public-url https://myapp.example.com` |

The [`docker/docker-compose.yml`](https://github.com/oblien/openship/blob/main/docker/docker-compose.yml) file orchestrates the full stack, including the OpenResty edge container alongside Postgres, Redis, the API, and the Dashboard.

## Practical Code Examples for Edge Configuration

You can programmatically generate the baked configuration, adjust real-IP settings, and create vhosts using the adapters package.

### Generate the Baked [`nginx.conf`](https://github.com/oblien/openship/blob/main/nginx.conf)

```typescript
import { bakedEdgeNginxConf } from "./infra/edge-baked-conf";
import { writeFileSync } from "fs";

writeFileSync("apps/edge/nginx.conf", bakedEdgeNginxConf());

```

This writes the fully assembled configuration that mirrors the runtime-patched bare edge.

### Add Trusted Proxies at Runtime

```typescript
process.env.OPENSHIP_EDGE_TRUSTED_PROXES = "10.0.0.0/8,172.16.0.0/12";

import { edgeRealIpConf } from "./infra/edge-real-ip";
console.log(edgeRealIpConf());

```

The `edgeRealIpConf` function emits the updated `real_ip` block with the additional CIDR ranges.

### Create a Custom Vhost for a New Domain

```typescript
import { writeFileSync } from "fs";
import { EDGE_NOT_FOUND_LOCATION } from "./infra/edge-not-found";

const vhost = `
server {
  listen 80;
  server_name myapp.example.com;
  ${EDGE_NOT_FOUND_LOCATION}
  # other directives (proxy_pass, etc.) will be added by the control plane

}
`;
writeFileSync("/etc/nginx/sites-enabled/myapp.conf", vhost);

```

After writing the file, the control plane triggers an NGINX reload through [`apps/api/src/modules/projects/edge-config.service.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/modules/projects/edge-config.service.ts).

## Summary

- Openship's edge layer is a unified OpenResty (NGINX + Lua) process that handles all traffic routing, domain management, and TLS termination.
- Configuration parity between containerized and bare-metal edges is enforced by `bakedEdgeNginxConf` in [`packages/adapters/src/infra/edge-baked-conf.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/infra/edge-baked-conf.ts) and validated in CI.
- Real-IP settings, unrouted host responses, and shared Lua dictionary sizes are generated from TypeScript constants to eliminate runtime mismatches.
- Custom domains are onboarded by writing vhost fragments to `sites-enabled` and reloading OpenResty, with automatic ACME HTTP-01 certificate provisioning via Certbot.
- Edge operations are exposed through the CLI at [`apps/cli/src/commands/edge.ts`](https://github.com/oblien/openship/blob/main/apps/cli/src/commands/edge.ts), including preflight checks and configuration import.

## Frequently Asked Questions

### How does Openship prevent configuration drift between container and bare-metal edges?

The `bakedEdgeNginxConf` generator in [`packages/adapters/src/infra/edge-baked-conf.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/infra/edge-baked-conf.ts) produces the complete [`nginx.conf`](https://github.com/oblien/openship/blob/main/nginx.conf) from shared constants such as `EDGE_SHARED_DICTS` and `EDGE_CLIENT_MAX_BODY_SIZE`. The container image includes a pre-generated copy of this file, and a CI test asserts that the generator output matches the baked copy exactly. This guarantees that both deployment modes use identical NGINX directives.

### What happens when a request reaches the OpenResty edge for an unrouted domain?

The edge serves a minimal HTML page defined by `EDGE_NOT_FOUND_HTML` in [`packages/adapters/src/infra/edge-not-found.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/infra/edge-not-found.ts). This response includes the sentinel string `openship-edge-unrouted` and is embedded directly in the default `location /` block via `EDGE_NOT_FOUND_LOCATION`. It functions identically whether the edge is running inside Docker or on a bare-metal server.

### How does Openship handle TLS certificates for custom domains?

The default HTTP server proxies ACME HTTP-01 challenges to Certbot's temporary listener, as configured in [`packages/adapters/src/infra/openresty-lua.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/infra/openresty-lua.ts). The HTTPS catch-all initially loads a self-signed placeholder certificate from `edgeDefaultCertPaths`. Once a domain is added, the control plane obtains a Let's Encrypt certificate, updates the vhost, and reloads OpenResty through [`apps/api/src/modules/projects/edge-config.service.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/modules/projects/edge-config.service.ts).

### Can Openship run behind an existing CDN or reverse proxy like Cloudflare?

Yes. The [`packages/adapters/src/infra/edge-real-ip.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/infra/edge-real-ip.ts) module configures the `real_ip` module to trust Cloudflare's CIDR ranges by default and reads the `CF-Connecting-IP` header. Operators can override the header via `OPENSHIP_EDGE_REAL_IP_HEADER` and specify additional trusted proxies to preserve correct client IP addresses for logging and rate limiting.