Guides

Custom proxy config

Add your own OpenResty directives to one domain, who may do it, what is refused, and how Synq tests the config and rolls it back if the edge rejects it.

Synq writes the OpenResty (nginx) configuration for every domain itself. For the few cases its settings don't cover, an organization admin can add a handful of raw directives to one domain. Synq checks them, tests the whole edge configuration and puts the previous version back automatically if the edge refuses it, so a typo can't take your other sites down.

Try the built-in settings first

Most needs have a safer switch. Upload size, timeouts, buffering and compression are in the project's Routing card on the Domains & Routes tab (Request limits); rewrites, redirects and response headers are there too; redirects, basic auth and IP rules are route rules. Setting the same directive in both places makes the edge test fail with a duplicate directive, and your change is rolled back.

Who can use it

  • Reading: anyone who can see the domain can open the dialog and view the rendered config.
  • Editing: organization owners and admins. Members with the Member role see "Only organization admins can change this. You can still see the rendered config." The API needs the domain:admin permission.
  • Availability: every domain of a project on your servers, including free subdomains on your apps domain (they are ordinary domains). It isn't offered for a managed subdomain this server doesn't route ("Custom config is not available for this domain.").

Edit a domain's config

Open the editor

Go to Domains in the sidebar, open the domain's ⋯ menu and choose Custom config. The dialog is titled "Custom config for {hostname}".

Write your directives

There are two boxes:

BoxWhere the lines go
Server blockInto this domain's HTTP (port 80) and HTTPS (port 443) server blocks, after Synq's own settings. Extra location blocks go here.
location / blockAt the end of the domain's location /, after the upstream and its proxy headers.

A byte counter under each box shows "{used} / {max} bytes".

Save and apply

Press Save and apply. The result appears in the dialog (below).

To remove your directives, empty both boxes and save.

What happens when you save

  1. Validation. The API checks every line before anything is stored (see Rules). A refused snippet is not saved; the error lists each problem with its box and line, for example server line 2: `include` is not allowed: it loads other files into the edge config.
  2. Apply. The config is stored on the domain and the project's live routes are re-applied. The edge writes the new vhost file, runs openresty -t on the whole configuration and reloads.
  3. Rollback. If the test or the reload fails, the edge restores the previous vhost file and Synq restores the previous stored config. Your draft stays in the boxes so you can fix it.

The dialog reports one of:

ResultMeaning
"Applied. The edge accepted the new config and reloaded."Live now.
"Saved. It will be applied when the project deploys."The project has no live deployment yet; the config goes live with the first deploy.
"The edge rejected this config, so the previous config was restored. Its error:"Followed by nginx's own error message. Nothing changed on the server.
"Nothing changed."The boxes match what's stored.

Your directives are stored with the domain and rendered into its vhost every time Synq writes it (a deploy, a certificate renewal, a route change), so they survive redeploys.

Every save that reaches the edge is recorded in the Audit log as domain.custom_config_updated or, when the edge refused it, domain.custom_config_rejected. The entry records line counts, sizes and directive names, not the raw text, since a snippet may contain a header value or token.

Rendered config

Rendered config (read-only) shows the domain's vhost file exactly as the edge holds it. Your lines are highlighted between the markers # synq-custom-config: begin (server) (or (location)) and # synq-custom-config: end. Press Refresh to read it again. Before the domain is verified or the project has deployed, it says that no vhost exists yet.

Rules

Raw config can't be proven safe by reading it, so the checks are layered: Synq refuses what must never reach a shared edge, checks the structure, and then lets openresty -t have the final word.

Size and structure

  • At most 8,192 bytes and 200 lines per box, and blocks nested at most 4 deep.
  • Every statement ends with ;, and braces must balance. A snippet can't close the block it's placed in.
  • No braces inside quoted values or comments, quoted values stay on one line, and no control characters.
  • Put location blocks in the Server block box, not in location / block.

Refused directives

The dialog summarizes the list: "Not allowed: include, load_module, Lua and njs code, init_* and worker_* settings, env, user, certificate paths, temp and cache paths, logs or root/alias outside this app, and upstreams on Synq's own ports." In full:

RefusedWhy
include, load_moduleLoad other files or native code into the edge.
Anything starting with lua, anything containing _by_lua, js_* (njs), perl*Run code on the edge.
init_*, worker_*, env, user, daemon, master_process, pid, lock_file, working_directory, thread_pool, pcre_jit, timer_resolution, ssl_engine and similarProcess-level settings of the shared edge.
ssl_certificate*, ssl_password_file, ssl_trusted_certificate, ssl_client_certificate, ssl_crl, ssl_dhparam, ssl_session_ticket_key, ssl_stapling_file, ssl_conf_commandCertificates are managed by Synq.
*_temp_path, *_cache_path, *_store, dav_*, xslt_*, auth_basic_user_file, create_full_put_pathRead or write files outside the route. Use basic auth rules instead of auth_basic_user_file.
listen, server_name, server, http, upstream, events, stream, mailWould change which traffic reaches the server block.
map, geo, split_clientsDeclare shared state for every route.

Directives with restricted arguments

DirectiveAllowed
access_log, error_logOnly files under /var/log/nginx/, /var/log/openresty/ or /usr/local/openresty/nginx/logs/, or access_log off.
root, aliasOne absolute path, without variables or .., inside the app's own static files.
proxy_pass, fastcgi_pass, uwsgi_pass, scgi_pass, grpc_pass, memcached_passA literal address (no variables), not a Unix socket, and not the loopback ports Synq itself uses (the API, the dashboard and OpenResty's management port).

Examples

Add an HSTS header, so browsers remember to use HTTPS (Server block):

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Keep response headers in the Server block box. An add_header inside location / makes nginx ignore every add_header from the server level for that location, including headers Synq sets for you.

Answer robots.txt at the edge for a staging site (Server block):

location = /robots.txt {
    default_type text/plain;
    return 200 "User-agent: *\nDisallow: /\n";
}

Hide a header your framework adds and pass an extra one to the app (location / block):

proxy_hide_header X-Powered-By;
proxy_set_header X-Forwarded-Host $host;

From the API

curl -H "Authorization: Bearer $SYNQ_TOKEN" \
  "$SYNQ_URL/api/domains/<domainId>/custom-config"

curl -X PUT -H "Authorization: Bearer $SYNQ_TOKEN" -H "Content-Type: application/json" \
  -d '{"server":"add_header X-Frame-Options \"DENY\" always;","location":""}' \
  "$SYNQ_URL/api/domains/<domainId>/custom-config"

GET returns the stored server and location text, whether you may edit it, the limits and the rendered vhost. PUT replaces both parts (an omitted field clears that part) and returns the same view plus a status of applied, saved, rolled_back or unchanged; a rolled_back result carries the edge's error in message.

On this page