Domain and Reverse Proxy
Configuration for serving Studio over a domain (especially HTTPS) instead of an IP address. This section must be completed in order. If any step is skipped, you will see seemingly unrelated symptoms: "preview shows blank screen," "build chat connection error," "deployed app returns 401," and so on.
One domain for everything
Preview and deployed app are served under the same domain as Studio, at /container/.
https://studio.company.com/ → 스튜디오 UI + API
https://studio.company.com/container/… → 프리뷰 · 배포앱
All you need is one DNS record, one certificate, one vhost.
Before 2026-08-23, the app was separated to a different host (studio-apps.company.com) and you had to tell
Studio its address via APPS_ORIGIN or the "App Origin" setting. That setting no longer exists.
We removed it because if that one value was empty, the server would generate preview URLs
as 요청호스트:5171, and in a reverse-proxy setup that only exposes ports 80/443, the browser could
not reach them — live preview would be completely blank. Using only relative paths
means there is no way to get it wrong.
Even if an old APPS_ORIGIN value remains in .env or environment settings, it is now not read.
Preview and deployed app contain code generated by chat, so untrusted content. The old structure isolated origins so that JavaScript could not access Studio login tokens. Now they run on the same origin, so that isolation is gone. We made this trade-off to reduce the domain to one.
Therefore do not grant app creation permission to untrusted users. This architecture assumes the builder role is given only to internal operators.
Checklist
- DNS A record: one only
- TLS certificate for that hostname
- Reverse proxy can reach Studio box :80
- Studio box firewall allows proxy → :80
The app listener (:5171) is served by web nginx inside the Studio box, which forwards
/container/ · /apps/ · /preview/, so there is no need to expose it outside. Only open the
firewall port in installations that access directly by port without a reverse proxy.
1. Add vhost to proxy
The external proxy should forward everything to Studio box :80. Branching for /container/ ·
/apps/ · /preview/ is already handled by web nginx inside the box.
Four essential settings
Symptoms differ when any is missing, making root cause hard to find. We recommend copying the example below exactly as-is.
| Setting | Symptom if missing |
|---|---|
proxy_set_header Host $http_host | Routing and link generation become misaligned |
Upgrade / Connection header | Live reload (HMR) WebSocket breaks; code changes don't appear in preview |
proxy_read_timeout 3600s | Build chat shows "connection error: network error" — when the agent is silent for dozens of seconds during tool execution, the default 60-second timeout closes the stream |
proxy_buffering off | Streaming response buffers; answers arrive in chunks with delay |
Build chat and Q&A stream in real time via SSE. The server sends heartbeat every 25 seconds,
so proxy timeout just needs to be longer, but 3600s is the value validated in production.
nginx config example
server {
listen 80;
server_name studio.company.com;
location / {
proxy_pass http://<studio-host>:80;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s; # 에이전트 SSE(빌드 채팅) — 필수
proxy_buffering off; # 스트리밍 즉시 전달 — 필수
client_max_body_size 64M; # 사진·도면 첨부
}
}
For HTTPS, issue a certificate with certbot or similar — the listen 443 ssl block will inherit
the config above.
If you mount vhost files directly into conf.d of the packaged web nginx container, make
the filename start with zz-. nginx picks the first server block alphabetically as
the default server, so if your name comes first, all unmatched requests fall through
elsewhere, and the entire Studio dies (this has actually happened).
2. Re-login (mandatory)
After configuring the domain, all users must log out and log back in once.
Login cookies are host-specific at the time of issue. Sessions logged in via IP will not carry over when you switch to the domain, and data requests will fail with 401.
3. Verification
| Item | How to check |
|---|---|
| Studio loads | Access https://studio.company.com → log in |
| Preview loads | Open project → preview displays, no Mixed Content in console |
| App path is open | Access https://studio.company.com/container/ → 404 or app list is normal (connected) |
| Live reload | Edit text in chat → preview auto-refreshes |
| Long build | Run build chat for 1+ minute → completes without "connection error" |
| Deployed app data | Open deployed app → shows live data (no 401) |
Cautions
After adding the domain, access only by domain. Users who access by IP will have different cookies, and deployed app data will return 401.
- WebSocket is supported by default, no additional config needed.
- The free universal certificate covers only single-level subdomains (
studio.company.com✅,apps.studio.company.com❌).
Field photos and drawings are attached to chat, so add client_max_body_size 64M.
Without it, uploading large photos will fail with 413.
HTTPS-only without reverse proxy
If you have no separate proxy server and terminate TLS directly on the Studio box, use the TLS overlay from the operations package.
cd /opt/kopens/plantpulse-studio-docker
cp tls/nginx-tls.conf.example tls/nginx-tls.conf # server_name 등 수정
# tls/cert.pem, tls/key.pem 배치(사내 CA 또는 공인 인증서)
docker compose -f docker-compose.yml -f docker-compose.tls.yml up -d
tls/nginx-tls.conf.example lacks /container/ blocks, and comments still mention the (now-removed)
APPS_ORIGIN and a separate port (5443). Using it as-is will cause preview and deployed app
addresses (/container/…) to return 404.
For now, make a copy (tls/nginx-tls.conf) and add /container/ blocks manually — use the same four headers
from the nginx config example above, and forward without the prefix
to the app listener. Do not add APPS_ORIGIN lines (they are not read).
Symptom → cause quick reference
These are common real-world issues with domain deployment. See Troubleshooting for broader coverage.
| Symptom | Cause | Action |
|---|---|---|
Preview and app are blank; console shows Mixed Content | Studio is HTTPS but app assets request HTTP | Add X-Forwarded-Proto $scheme to proxy, then make everything HTTPS |
| Deployed app data returns 401 | Session logged in before adding domain | Log out and re-login once |
| Build chat times out with "connection error: network error" | Proxy idle timeout (default 60s) | proxy_read_timeout 3600s |
| Code edits don't appear in preview | HMR WebSocket not upgraded | Add Upgrade / Connection headers |
| Answers arrive in chunks with delay | proxy_buffering is on (default) | proxy_buffering off |
| Random domain falls through to wrong place | web nginx conf.d load order (first server is default server) | Start vhost filename with zz- |
/container/… returns 404 | TLS overlay copy missing /container/ block | See warning above |
| App won't load in port-direct installation | Firewall blocks 5171 | Open firewall, then check with curl -I http://<host>:5171/ |
Related docs
- Installation — ports and firewall
- Secret management — how changes take effect
- Troubleshooting