# Run in production at scale

### Docker

```bash
docker run -d \
  --name obscura \
  --restart unless-stopped \
  -p 127.0.0.1:9222:9222 \
  -v /srv/obscura/data:/data \
  h4ckf0r0day/obscura \
  serve --host 0.0.0.0 --storage-dir /data --stealth
```

The image runs `obscura serve` by default. Override with arguments after the image name.

### Systemd

`/etc/systemd/system/obscura.service`:

```ini
[Unit]
Description=Obscura headless browser
After=network.target

[Service]
ExecStart=/usr/local/bin/obscura serve --port 9222 --stealth --storage-dir /var/lib/obscura
Restart=always
RestartSec=5
User=obscura
Group=obscura
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
```

```bash
systemctl enable --now obscura
journalctl -fu obscura
```

### Workers

`obscura serve --workers N` runs N CDP server workers behind the listener.

```bash
obscura serve --workers 4
```

Use one worker per CPU core. Each worker handles its own pool of pages. Sessions are sticky to a worker.

### V8 heap

Default V8 heap is 4 GB on 64-bit systems. Override:

```bash
obscura serve --v8-flags "--max-old-space-size=2048"
```

Lower for memory-constrained hosts, raise for heavy SPAs.

### Parallel scrape

`obscura scrape` fans out a list of URLs across worker processes:

```bash
obscura scrape \
  --concurrency 20 \
  --format json \
  --timeout 60 \
  url1 url2 url3 ...
```

Reads URLs from stdin:

```bash
cat urls.txt | obscura scrape --concurrency 20 -
```

Requires `obscura-worker` next to `obscura` in `PATH`.

### Resource limits

Per-process memory cap with systemd:

```ini
[Service]
MemoryMax=4G
MemoryHigh=3G
```

Per-container with Docker:

```bash
docker run --memory=4g --cpus=2 ...
```

### Reverse proxy

Expose obscura on TLS through nginx or caddy:

```nginx
location /obscura/ {
  proxy_pass http://127.0.0.1:9222/;
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";
  proxy_read_timeout 86400;
}
```

CDP needs WebSocket upgrade and long read timeouts.

### Authentication

Obscura's CDP server has no built-in auth. Anyone who can reach the port can drive the browser. Options:

* Bind to `127.0.0.1` and require SSH for access (default).
* Put it behind a reverse proxy that enforces auth.
* Use Docker network isolation.

Never bind `0.0.0.0` on a public IP without one of the above.

### Observability

```bash
obscura serve --verbose
RUST_LOG=obscura=debug obscura serve
```

`--verbose` enables info-level logs. `RUST_LOG=obscura=debug` enables debug-level. Logs go to stderr.

### Reliability and timeouts

The engine is hardened so one page cannot hang, crash, or wedge a worker. A V8 watchdog terminates runaway scripts and microtask storms, DOM ops are panic-safe, cyclic DOM mutations are rejected, and the CDP server terminates any single command that overruns its budget so a hung session cannot stall the others. Scripted `fetch()`/XHR and navigation are timeout-bounded. You can point the server at arbitrary or heavy pages without a stuck worker.

Tune the bounds with environment variables (see [Environment variables](/reference/environment-variables.md)):

```bash
OBSCURA_NAV_TIMEOUT_MS=60000 \
OBSCURA_CDP_COMMAND_TIMEOUT_MS=30000 \
OBSCURA_FETCH_TIMEOUT_MS=20000 \
  obscura serve
```

`OBSCURA_NAV_TIMEOUT_MS` is the per-navigation ceiling (default 30000). `OBSCURA_CDP_COMMAND_TIMEOUT_MS` is the per-CDP-command V8 deadline (default 60000, `0` disables). `OBSCURA_FETCH_TIMEOUT_MS` bounds scripted fetch/XHR and module loads (default 30000).


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.obscura.sh/guides/run-in-production-at-scale.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
