{"result":{"depiction_url":null,"edges":{"observes":{"objects":[{"created":"2026-11-12T13:54:57Z","object_id":{"id":1466,"is_a":["text","documentation","reference","notification"],"name":"doc_notification_admin_menu","title":"observe_admin_menu\/3","uri":"https:\/\/zotonic.com\/id\/1466"},"seq":1},{"created":"2026-11-12T13:54:57Z","object_id":{"id":2158,"is_a":["text","documentation","reference","notification"],"name":"doc_notification_m_config_update","title":"observe_m_config_update\/2","uri":"https:\/\/zotonic.com\/id\/2158"},"seq":2},{"created":"2026-11-12T13:54:57Z","object_id":{"id":2319,"is_a":["text","documentation","reference","notification"],"name":"doc_notification_middleware","title":"observe_middleware\/3","uri":"https:\/\/zotonic.com\/id\/2319"},"seq":3},{"created":"2026-11-12T13:54:57Z","object_id":{"id":3148,"is_a":["text","documentation","reference","notification"],"name":"doc_notif_mod_analytics_analytics_events_829ef7875e812ce7925f9a38505bca63","title":"observe_analytics_events(Notification, Acc, Context)","uri":"https:\/\/zotonic.com\/id\/3148"},"seq":4}],"predicate":{"id":2550,"is_a":["meta","predicate"],"name":"observes","title":{"_type":"trans","tr":{"en":"Observes"}},"uri":"https:\/\/zotonic.com\/id\/observes"}},"subject":{"objects":[{"created":"2026-11-12T13:54:57Z","object_id":{"id":2555,"is_a":["categorization","keyword","keyword_information_type"],"name":"zotonic_topic_reference","title":"Reference","uri":"https:\/\/zotonic.com\/id\/2555"},"seq":1},{"created":"2026-11-12T13:54:57Z","object_id":{"id":2564,"is_a":["categorization","keyword","keyword_audience"],"name":"zotonic_topic_backend_developer","title":"Backend developer","uri":"https:\/\/zotonic.com\/id\/2564"},"seq":2},{"created":"2026-11-12T13:54:57Z","object_id":{"id":2566,"is_a":["categorization","keyword","keyword_audience"],"name":"zotonic_topic_operator","title":"Operator","uri":"https:\/\/zotonic.com\/id\/2566"},"seq":3},{"created":"2026-11-12T13:54:57Z","object_id":{"id":2607,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_logging_and_monitoring","title":"Logging and monitoring","uri":"https:\/\/zotonic.com\/id\/2607"},"seq":4},{"created":"2026-11-12T13:54:57Z","object_id":{"id":2635,"is_a":["categorization","keyword","keyword_architecture"],"name":"zotonic_topic_module","title":"Module","uri":"https:\/\/zotonic.com\/id\/2635"},"seq":5},{"created":"2026-11-12T13:54:57Z","object_id":{"id":2685,"is_a":["categorization","keyword","keyword_quality"],"name":"zotonic_topic_security","title":"Security","uri":"https:\/\/zotonic.com\/id\/2685"},"seq":6},{"created":"2026-11-12T13:54:57Z","object_id":{"id":2688,"is_a":["categorization","keyword","keyword_quality"],"name":"zotonic_topic_performance","title":"Performance","uri":"https:\/\/zotonic.com\/id\/2688"},"seq":7}],"predicate":{"id":308,"is_a":["meta","predicate"],"name":"subject","title":{"_type":"trans","tr":{"en":"Keyword"}},"uri":"http:\/\/purl.org\/dc\/elements\/1.1\/subject"}}},"export_revision":25,"id":3192,"is_a":["text","documentation","reference","module"],"links":[{"rel":"self","target":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/3192"},{"rel":"hub","target":"https:\/\/zotonic.com\/.zotonic\/websub"}],"medium":null,"medium_url":null,"name":"doc_module_mod_traffic_guard","page_url":{"en":"https:\/\/zotonic.com\/docs\/3192\/mod_traffic_guard","x-default":"https:\/\/zotonic.com\/docs\/3192\/mod_traffic_guard"},"preview_url":null,"resource":{"version":3,"pivot_location_lat":null,"title":"mod_traffic_guard","is_authoritative":true,"body":"<p>Observe or throttle rapid content retrieval without trusting browser user agents.\nEnable <code>mod_traffic_guard<\/code> in Modules. The default mode is <strong>observe<\/strong>, which allows\nall requests and reports when a configured budget would reject one. <strong>enforce<\/strong>\nreturns HTTP 429 with Retry-After and no-cache headers. <strong>off<\/strong> bypasses checks.<\/p>\n<h2>Scope and configuration<\/h2>\n<p>The guard covers GET\/HEAD requests to page, template, HTTP model API and agent\ninterface controllers. Static assets, POST requests and MQTT messages are excluded.\nCustom controllers classified as pages by analytics (<code>{analytics, page}<\/code>) are also\ncovered, even without analytics enabled. Dispatch option <code>{traffic_guard, true}<\/code>\nopts another controller in; <code>false<\/code> opts a\nroute out. Configuration is under <code>mod_traffic_guard<\/code> in System Configuration.<\/p>\n<table class=\"table\"><thead><tr><th>Key<\/th><th>Default<\/th><th>Meaning<\/th><\/tr><\/thead><tbody><tr><td>mode<\/td><td>observe<\/td><td>off, observe, enforce<\/td><\/tr><tr><td>period<\/td><td>60<\/td><td>Rate period, in seconds<\/td><\/tr><tr><td>request_limit<\/td><td>120<\/td><td>Requests replenished per period<\/td><\/tr><tr><td>request_burst<\/td><td>30<\/td><td>Maximum request credits<\/td><\/tr><tr><td>max_clients<\/td><td>5000<\/td><td>Retained client budgets per site\/node<\/td><\/tr><\/tbody><\/table>\n<p>Budgets are shared by all covered routes for a client IP. Cookies and user agents\ncannot reset them. There are no automatic admin or claimed-bot exemptions. Shared\nnetworks share budgets: calibrate in observe mode before enforcing. Invalid values\nfall back to defaults. Configuration changes reset transient budgets on the next\ncheck. Off mode performs no detection or analytics emission.<\/p>\n<h2>IP exceptions<\/h2>\n<p>Use <strong>System → Traffic guard<\/strong> to edit exact IPv4 and IPv6 allow\/block lists (up to\n256 addresses per list). Allow takes precedence and bypasses rate limits. Block\nentries follow the mode: observe reports would_throttle, enforce returns 429, off\nbypasses checks. Lists cover only guarded routes and methods. Saves require a\nwritable administrator and invalidate the in-memory copy. Invalid entries reject\nthe whole save. IPv4-mapped IPv6 addresses match IPv4 entries. CIDR networks and\nhostnames are not supported. Entries are stored in <code>traffic_guard_ip<\/code>, with a required note and optional UTC\nexpiry. Expired rules stop applying immediately; hourly cleanup deletes them.\nThe admin page shows currently rate-limited IPs on this node and offers timed\nblocks with automatically generated notes. Global <code>traffic_guard_allow<\/code> and\n<code>traffic_guard_block<\/code> lists in zotonic.config are merged on settings load and\nshown read-only. See <code>m_traffic_guard<\/code> for model APIs.<\/p>\n<h2>Analytics<\/h2>\n<p>With <code>mod_analytics<\/code> enabled, the default Page requests chart includes Would\nthrottle and Throttled lines for <code>other<\/code> and <code>unknown<\/code> page attempts. A separate\ncompanion recording reserves 10,734 rows within the site budget, preserving the\nexisting page-view history. Additional breakdowns can use <code>mod_traffic_guard:request<\/code>.\nEach checked request emits one decision, including early rejections. Dimensions\nare <code>crawl_behavior<\/code> (normal, excessive, unknown), <code>guard_action<\/code> (allow,\nwould_throttle, throttle), <code>guard_reason<\/code> (none, request_rate,\nstate_capacity, unavailable, ip_allow, ip_block), <code>traffic<\/code>, and <code>type<\/code> (page, api, other).\nExisting request events also receive the three guard dimensions. Unknown means\nno reliable guard assessment was available, not a suspected crawler.\nAnalytics consent and admin exclusions still apply; enforcement never depends on\nanalytics availability or recording configuration. Guard events and ordinary\nrequest events overlap and should not be added together as a request total.<\/p>\n<h2>Limits and privacy<\/h2>\n<p>State is transient and bounded: only client IPs and request-rate budgets are\ntracked, with no paths, query strings or cookies. Every covered request consumes\nthe same budget, including repeated reloads of one page. Idle clients expire and\na restart resets all budgets. Limits are node-local, not shared across a cluster.<\/p>\n<p>Unavailable or busy workers and exhausted client capacity fail open with an\nunknown assessment. This is origin protection, not a DDoS firewall. Use trusted\nproxy configuration for the client IP and proxy-level protection for distributed\nattacks. This version does not implement fingerprinting, challenges, account\nbudgets, concurrency limits, or bot verification. See the module README for setup\nand validation examples.<\/p>","slug":"mod_traffic_guard","doc_module_config":[{"default":"&lt;&lt;&quot;observe&quot;&gt;&gt;","description":"off, observe or enforce. Observe reports excess without blocking.","has_default":true,"key":"mode","module":"mod_traffic_guard","type":"string"},{"default":"60","description":"Rate period in seconds (1..3600).","has_default":true,"key":"period","module":"mod_traffic_guard","type":"integer"},{"default":"120","description":"Sustained requests per period (1..100000).","has_default":true,"key":"request_limit","module":"mod_traffic_guard","type":"integer"},{"default":"30","description":"Immediate request allowance (1..1000).","has_default":true,"key":"request_burst","module":"mod_traffic_guard","type":"integer"},{"default":"5000","description":"Maximum retained client budgets per site and node (1..50000).","has_default":true,"key":"max_clients","module":"mod_traffic_guard","type":"integer"}],"is_protected":false,"visible_for":0,"tz":"UTC","language":["en"],"doc_source_hash":"c9535f690241aa7a93de1e44b376c3406e7c479b987cc2816e46eb6c385d4930","is_featured":false,"content_group_id":{"id":2551,"is_a":["meta","content_group"],"name":"content_group_imported_docs","title":"Imported documentation","uri":"https:\/\/zotonic.com\/id\/content_group_imported_docs"},"category_id":{"id":320,"is_a":["meta","category"],"name":"module","title":"Modules","uri":"https:\/\/test.zotonic.com\/id\/320"},"doc_source_path":"apps\/zotonic_mod_traffic_guard\/src\/mod_traffic_guard.erl","publication_start":"2026-11-12T13:54:57Z","github_url":"https:\/\/github.com\/zotonic\/zotonic\/blob\/master\/apps\/zotonic_mod_traffic_guard\/src\/mod_traffic_guard.erl","pivot_location_lng":null,"doc_source_kind":"module","name":"doc_module_mod_traffic_guard","is_unfindable":false,"is_published":true,"pivot_geocode":null,"created":"2026-11-12T13:54:57Z","uri":null,"doc_status":"current","is_dependent":false,"erlang_app":"zotonic_mod_traffic_guard","publication_end":"9999-06-01T00:00:00Z","modifier_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"privacy":0,"doc_source_commit":"ae7768c74c4641c51de9824d192702d7f5526239\n","creator_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"modified":"2026-11-12T14:44:19Z","title_slug":"mod_traffic_guard"},"uri":"https:\/\/zotonic.com\/id\/3192","uri_template":"https:\/\/zotonic.com\/id\/:id","version":25,"websub":{"hub":"https:\/\/zotonic.com\/.zotonic\/websub","topic":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/3192"}},"status":"ok"}