TCP and UDP ports¶
A tcp service passes TCP and forwards UDP to your origins, for example for VPNs;
anyconnect fronts a Cisco Secure VPN gateway. The mode is set at creation.
Endpoints¶
| Method | Path | Key | Purpose |
|---|---|---|---|
PATCH |
/services/{fqdn} |
write | Change a vService |
PATCH /services/{fqdn}
Body: ServicePatch
Returns: 200, Service
Request: waf_and_limit
{
"waf": {
"enabled": true
},
"rate_limiting": {
"req_per_10s": 800
}
}
Request: replace_origins
{
"origins": [
{
"name": "origin-1",
"address": "192.0.2.10",
"port": 443
}
]
}
Answer 200: waf_and_limit
{
"fqdn": "a.example.com",
"tenant": "tenant-a",
"enabled": true,
"mode": "https",
"ports": [
443
],
"udp_ports": [],
"created": "2026-10-01T00:00:00+00:00",
"created_by": "",
"account_id": "acc-a",
"network": {
"vips": [
{
"anycast_ipv4": "203.0.113.1",
"anycast_ipv6": "2001:db8:a::1"
}
],
"pool": null,
"edge_hostname": "a-example-com.edge.protect.zeroservices.eu"
},
"network_protection": {
"enabled": true
},
"backend": {
"algorithm": "roundrobin",
"health_check": {}
},
"origins": [
{
"name": "origin-1",
"address": "192.0.2.10",
"port": 443,
"tls": true,
"ssl_verify": true,
"weight": 100,
"maxconn": 100
}
],
"waf": {
"enabled": true,
"fail_open": true,
"mode": "On",
"paranoia_level": 1,
"inbound_threshold": 5,
"disabled_rules": []
},
"rate_limiting": {
"challenge_req_per_10s": 200,
"req_per_10s": 800,
"conn_per_10s": 50,
"conn_drop_per_10s": 500,
"path_default": 1000,
"path_aggregate_per_10s": 5000,
"geo_tcp_reject_sess_rate": 200,
"ja4_enabled": false,
"ja4_req_per_10s": 100,
"ja4_action": "challenge"
},
"pow": {
"enabled": true,
"difficulty": 18,
"cookie_ttl": 86400,
"always_challenge": false,
"exempt_crawlers": true,
"bind_ip": true,
"botcheck_per_10s": 1000
},
"admission": {
"enabled": false,
"credit_required": 5,
"credit_window": "60s",
"known_ttl": "24h",
"unknown_conn_rate": 1500,
"table_size": "1m"
},
"ssl": {
"mode": "acme",
"acme_provider": "zerossl",
"acme_email": null,
"https_redirect": false,
"cert_status": "none",
"cert_issued": null,
"cert_expires": null,
"cert_last_renewal": null,
"days_remaining": null
},
"aliases": [
{
"fqdn": "www.a.example.com",
"ssl": {
"mode": "acme",
"acme_provider": "zerossl",
"acme_email": null,
"https_redirect": false,
"cert_status": "none",
"cert_issued": null,
"cert_expires": null,
"cert_last_renewal": null,
"days_remaining": null
}
}
],
"cache": {
"enabled": true
},
"slow_http": {
"enabled": true,
"timeout_http_request": "5s",
"timeout_client_fin": "5s"
},
"tcp_timeout": {
"client": "30s",
"server": "30s",
"tunnel": "30s"
},
"udp_timeout": {
"idle": "300s",
"unreplied": "30s",
"unreplied_pkts": 32,
"src_pps": 0
},
"sni_policy": "allowlist",
"proxy_protocol": false,
"tls_termination": false,
"priority": {
"enabled": true,
"session_signal": []
},
"response_headers": {
"hsts": {},
"cors": {},
"x_frame_options": null,
"x_content_type_options": false,
"referrer_policy": null
},
"content_negotiation": {
"enabled": false,
"encodings": [
"zstd",
"gzip"
],
"file_types": [
"css",
"js",
"html"
]
},
"logging": {
"enabled": false
},
"threat_intel": {
"mode": "log",
"abuse": "log",
"tor": "log"
},
"redirects": [],
"origin_host": null,
"origin_sni": null,
"maintenance_mode": false,
"auto_mitigation": false,
"defense_posture": null,
"protection_allowlist": null,
"edges": [],
"protection_rules": [],
"append_trailing_slash": false,
"exceed_no_rtbh": false,
"exceed_no_flowspec": false,
"origin_kind": "dynamic",
"alias_limit": 20,
"serving_edges": [
"shared-1"
],
"posture_drift": [],
"baseline": null
}
Answer 200: replace_origins
{
"fqdn": "a.example.com",
"tenant": "tenant-a",
"enabled": true,
"mode": "https",
"ports": [
443
],
"udp_ports": [],
"created": "2026-10-01T00:00:00+00:00",
"created_by": "",
"account_id": "acc-a",
"network": {
"vips": [
{
"anycast_ipv4": "203.0.113.1",
"anycast_ipv6": "2001:db8:a::1"
}
],
"pool": null,
"edge_hostname": "a-example-com.edge.protect.zeroservices.eu"
},
"network_protection": {
"enabled": true
},
"backend": {
"algorithm": "roundrobin",
"health_check": {
"method": "GET",
"path": "/",
"host": null,
"interval": "10s",
"rise": 2,
"fall": 3,
"expect": null
}
},
"origins": [
{
"name": "origin-1",
"address": "192.0.2.10",
"port": 443,
"tls": true,
"ssl_verify": true,
"weight": 100,
"maxconn": 100
}
],
"waf": {
"enabled": true,
"fail_open": true,
"mode": "On",
"paranoia_level": 1,
"inbound_threshold": 5,
"disabled_rules": []
},
"rate_limiting": {
"challenge_req_per_10s": 200,
"req_per_10s": 800,
"conn_per_10s": 50,
"conn_drop_per_10s": 500,
"path_default": 1000,
"path_aggregate_per_10s": 5000,
"geo_tcp_reject_sess_rate": 200,
"ja4_enabled": false,
"ja4_req_per_10s": 100,
"ja4_action": "challenge"
},
"pow": {
"enabled": true,
"difficulty": 18,
"cookie_ttl": 86400,
"always_challenge": false,
"exempt_crawlers": true,
"bind_ip": true,
"botcheck_per_10s": 1000
},
"admission": {
"enabled": false,
"credit_required": 5,
"credit_window": "60s",
"known_ttl": "24h",
"unknown_conn_rate": 1500,
"table_size": "1m"
},
"ssl": {
"mode": "acme",
"acme_provider": "zerossl",
"acme_email": null,
"https_redirect": false,
"cert_status": "none",
"cert_issued": null,
"cert_expires": null,
"cert_last_renewal": null,
"days_remaining": null
},
"aliases": [
{
"fqdn": "www.a.example.com",
"ssl": {
"mode": "acme",
"acme_provider": "zerossl",
"acme_email": null,
"https_redirect": false,
"cert_status": "none",
"cert_issued": null,
"cert_expires": null,
"cert_last_renewal": null,
"days_remaining": null
}
}
],
"cache": {
"enabled": true
},
"slow_http": {
"enabled": true,
"timeout_http_request": "5s",
"timeout_client_fin": "5s"
},
"tcp_timeout": {
"client": "30s",
"server": "30s",
"tunnel": "30s"
},
"udp_timeout": {
"idle": "300s",
"unreplied": "30s",
"unreplied_pkts": 32,
"src_pps": 0
},
"sni_policy": "allowlist",
"proxy_protocol": false,
"tls_termination": false,
"priority": {
"enabled": true,
"session_signal": []
},
"response_headers": {
"hsts": {
"enabled": false,
"max_age": 31536000,
"include_subdomains": false,
"preload": false
},
"cors": {
"enabled": false,
"rules": []
},
"x_frame_options": null,
"x_content_type_options": false,
"referrer_policy": null
},
"content_negotiation": {
"enabled": false,
"encodings": [
"zstd",
"gzip"
],
"file_types": [
"css",
"js",
"html"
]
},
"logging": {
"enabled": false
},
"threat_intel": {
"mode": "log",
"abuse": "log",
"tor": "log"
},
"redirects": [],
"origin_host": null,
"origin_sni": null,
"maintenance_mode": false,
"auto_mitigation": false,
"defense_posture": null,
"protection_allowlist": null,
"edges": [],
"protection_rules": [],
"append_trailing_slash": false,
"exceed_no_rtbh": false,
"exceed_no_flowspec": false,
"origin_kind": "dynamic",
"alias_limit": 20,
"serving_edges": [
"shared-1"
],
"posture_drift": [],
"baseline": null
}
Fields¶
Part of the service: send them with PATCH /services/{fqdn}.
| Field | Type | Notes |
|---|---|---|
ports |
list of integer or string | at most 8; may be null; TCP ports the edge accepts, up to 8 entries. https: 443 and 80 by default, any further port is plain HTTP to the same port on your origins. tcp: also ranges ("40000-40009") and mappings ("40000-40009=50000": edge range to origin start) |
udp_ports |
list of integer or string | at most 8; may be null; UDP ports of a tcp or anyconnect vService, up to 8 entries: forwarded to your origins, to the same port unless mapped (ranges and mappings tcp only). Not for https (no UDP listener). Empty: all UDP to the vService's addresses is dropped |
proxy_protocol |
boolean | may be null; tcp and anyconnect: send a PROXY protocol v2 header with the client address to your origins, before each TCP connection and each forwarded UDP datagram. Your origin must expect it |
tls_termination |
boolean | may be null; tcp only: the edge terminates TLS with the vService's certificate and opens its own connection to each origin as its tls and ssl_verify say. Until a certificate exists, TLS passes through |
| Entry | Edge ports | Origin ports |
|---|---|---|
443 |
443 | 443 |
"40000-40009" |
40000–40009 | the same |
"40000-40009=50000" |
40000–40009 | 50000–50009 |
Read-only fields¶
Set by support.
| Field | Type | Notes |
|---|---|---|
tcp_timeout |
TcpTimeout |
read-only; Set by support on request |
udp_timeout |
UdpTimeout |
read-only; Set by support on request |
sni_policy |
string | one of allowlist, off; default "allowlist"; read-only; Set by support on request |
TcpTimeout¶
| Field | Type | Notes |
|---|---|---|
client |
string | default "30s"; Time the edge waits for the client to send or acknowledge data: a number with the unit us, ms, s, m, h or d, such as 30s |
server |
string | default "30s"; Time the edge waits for your origin to send or acknowledge data: a number with the unit us, ms, s, m, h or d, such as 30s |
tunnel |
string | default "30s"; Inactivity timeout of tunnelled connections such as WebSocket, both directions: a number with the unit us, ms, s, m, h or d, such as 30s |
UdpTimeout¶
| Field | Type | Notes |
|---|---|---|
idle |
string | default "300s"; A flow without packets in either direction for this long ends: a number with the unit us, ms, s, m, h or d, such as 300s; at least 1 s |
unreplied |
string | default "30s"; A flow your origin has not answered within this long ends, so spoofed sources cannot hold flow entries: a number with the unit us, ms, s, m, h or d, such as 30s |
unreplied_pkts |
integer | 0–65535; default 32; Packets a flow may send before your origin answers; 0: no cap, for one-way protocols |
src_pps |
integer | 0–100000000; default 0; Forwarded packets per second per source over all its flows; 0: no limit |
Example¶
curl -s -X PATCH -H "Authorization: Bearer $ZP_KEY" -H 'Content-Type: application/json' \
$API/services/vpn.example.com \
-d '{"ports": [443, "40000-40009=50000"], "udp_ports": [51820], "proxy_protocol": true}'
Behaviour¶
- TCP and UDP are each optional; one protocol's entries must not overlap. With one TCP port the edge connects to the origin's
port; with several, each keeps its number. - Origins see edge addresses: allow them (support has the list).
proxy_protocol: a PROXY v2 header before each TCP connection and UDP datagram: client and service address and port; for TCP also the Ray ID (0x05) and, withsni_policy: allowlist, the server name (0x02). Set up the receiver first; trust edge addresses only:
| Software | Setting |
|---|---|
| nginx | listen … proxy_protocol; with set_real_ip_from <edge addresses>; |
| HAProxy | bind … accept-proxy |
| dnsdist | setProxyProtocolACL({<edge addresses>}) |
| PowerDNS | proxy-protocol-from=<edge addresses> |
tls_termination: the edge presents the service certificate (TLS) and connects per origintls/ssl_verify; TLS passes through until a certificate exists. Port 80 then serves only ACME HTTP-01 (using port 80 yourself: upload the certificate).- PROXY protocol and TLS at the edge do not work with the forward proxy for fixed outgoing addresses.
sni_policy: allowlistdrops foreign server names; VLESS with REALITY needsoff.
| Protection, per IPv4 address or IPv6 /64 | TCP | UDP |
|---|---|---|
| Blocklists, country/ASN/IP rules, malformed packets, fragments | yes | yes |
| New connections or flows | conn_per_10s |
conn_per_10s |
| Silent drop of a flooding source | conn_drop_per_10s |
— |
| From source ports 7, 17, 19, 53, 111, 123, 137, 161, 389, 427, 520, 623 | — | dropped |
| Packets before the origin answers | — | udp_timeout.unreplied_pkts; 0 for one-way protocols |
| Packets per second, all flows | — | udp_timeout.src_pps |
- UDP 53 is DNS: only queries pass; each query is a flow, so
conn_per_10s(default 50, Rate limits) is too low for busy resolvers. - A UDP client sticks to one origin across edges, spread by
weight; if that origin fails its health check, the client moves at once and stays.weight0 drains; without a healthy origin UDP stops; UDP-only origins count as healthy. udp_portsexist fortcpandanyconnectservices; anhttpsservice has none (no QUIC).anyconnect: TCP 443 (TLS) passes through, UDP 443 (DTLS) is forwarded; the health check verifies the certificate: send the intermediate.- IPv4 clients to an IPv6 origin: UDP at most 1480 bytes (WireGuard MTU 1420).
| After a client changes edge | |
|---|---|
| TCP, AnyConnect | client reconnects |
| WireGuard, QUIC (HTTP/3, Hysteria2) | continues |
| OpenVPN over UDP | needs float |
| Limit | |
|---|---|
| TCP or UDP entries per service | 8 each |
| Forwarded UDP ports, ranges per port | 1024 |
| UDP clients / TCP connections per edge, origin address and port | ~20,000 / ~32,000 |
In the portal: Advanced → Ports; PROXY protocol and TLS at the edge under Origins.