-
Notifications
You must be signed in to change notification settings - Fork 219
Description
Description
When running ODK Central behind a reverse proxy with SSL_TYPE=upstream, all Enketo form access fails (preview, public access links, submission editing). Enketo cannot fetch form definitions from the ODK API because it tries to connect over HTTPS:443, but nginx only listens on HTTP:80 in upstream mode.
Environment
- ODK Central: latest (
main) - Enketo:
ghcr.io/enketo/enketo:7.5.1 - Reverse proxy: Caddy (but applies to any reverse proxy setup using
SSL_TYPE=upstream) - Docker Compose with external reverse proxy on a separate Docker network
Expected behavior
All Enketo form access (preview, fill, edit) works when SSL_TYPE=upstream is set β Enketo successfully fetches form XML from the ODK API.
Observed behavior
Opening any form (preview, fill link, or edit) returns a 500 error:
{"code": 500, "message": "Could not connect with Form Server"}Reproducing from inside the Enketo container confirms that HTTPS to the domain is unreachable:
$ node -e "require('https').get('https://my-domain.example/').on('error', e => console.log(e.message))"
connect ECONNREFUSED 192.168.x.x:443
This is not a forced HTTPS call β it reproduces exactly what Enketo does internally when it reads the openRosaServer URL stored in Redis (which is always https://).
Root cause
Two parts of ODK Central contradict each other when SSL_TYPE=upstream:
-
setup-odk.shstrips all SSL from nginx:perl -i -ne 's/listen 443.*/listen 80;/; print if ! /\bssl_/' /etc/nginx/conf.d/odk.confAfter this, nginx only listens on port 80. Port 443 is completely gone.
-
The
servicecontainer registers forms with Enketo using anopenRosaServerURL that is alwayshttps://:openRosaServer = https://my-domain.example/v1/key/.../projects/1/forms/myform/draftThis URL is stored per-form in Redis and is what Enketo uses for all API calls to fetch form XML.
When Enketo tries to call https://my-domain.example, Docker DNS resolves it to nginx's IP, but nginx doesn't serve HTTPS β ECONNREFUSED on port 443.
This works fine with SSL_TYPE=letsencrypt, selfsign, or customssl because in all those modes nginx retains its HTTPS listener on port 443. Only upstream removes it.
What does NOT work as a fix
-
Setting
"server url": "http://..."in Enketo'sconfig.json.template: This only changes the default server URL in the config. The per-formopenRosaServerstored in Redis (set by theservicecontainer at registration time) remainshttps://and that's what Enketo actually uses for API calls. -
Adding a Docker DNS alias so Enketo resolves the domain to nginx: Enketo resolves the name correctly, but still tries HTTPS:443, which nginx doesn't serve.
Current workaround
We use extra_hosts: "${DOMAIN}:host-gateway" on the Enketo container so that https://my-domain.example routes through the Docker host to the external reverse proxy (Caddy), which has a valid TLS certificate and proxies back to nginx on HTTP:80.
# docker-compose.override.yml
services:
enketo:
extra_hosts:
- "${DOMAIN}:host-gateway"This works but adds an unnecessary round-trip through the reverse proxy for internal traffic.
Suggested fix
When SSL_TYPE=upstream, the service container should register forms with Enketo using http:// instead of https:// for the openRosaServer URL, since nginx is explicitly configured to not serve TLS in this mode.
Alternatively, setup-odk.sh could preserve a self-signed HTTPS listener on nginx in upstream mode (the self-signed cert is already generated at lines 19-23 of the script) so that internal HTTPS calls still work. This would require setting NODE_TLS_REJECT_UNAUTHORIZED=0 on the Enketo container.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status