{"id":977,"date":"2026-10-30T22:54:08","date_gmt":"2026-10-30T22:54:08","guid":{"rendered":"https:\/\/windows-vps.org\/blog\/?p=977"},"modified":"2026-10-30T22:54:08","modified_gmt":"2026-10-30T22:54:08","slug":"iis-arr-application-request-routing-websocket-keepalive-timeouts","status":"publish","type":"post","link":"https:\/\/windows-vps.org\/blog\/iis-arr-application-request-routing-websocket-keepalive-timeouts\/","title":{"rendered":"IIS ARR Keep-Alive and WebSocket Timeouts: Why Proxied Connections Die at 60 Seconds"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">An IIS site proxied through Application Request Routing that dies at almost exactly 60 seconds is not a bug in your application. It is the ARR proxy timeout, and the &#8220;exactly&#8221; is the tell: ARR&#8217;s default <code>proxyTimeout<\/code> is 120 seconds at the server level, but several templates and Azure App Service front ends inherit a 60-second idle limit upstream, and IIS&#8217;s own connection timeout defaults to 120. A WebSocket that pings every 90 seconds then gets torn down every time, with a 502 in the browser and no error in the app log.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is a timeout-chain problem. Three independent timers govern a proxied WebSocket \u2014 the upstream idle timeout, the ARR proxy timeout, and the application&#8217;s own keep-alive \u2014 and the connection lives only as long as the shortest of them minus the ping interval. The sections below map the chain, show how to prove which timer fired, and give a value table that keeps connections alive without leaking sockets. Baseline VPS sizing for the ARR role is on <a href=\"https:\/\/windows-vps.org\/\">the Windows VPS plan comparison<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The three timers in the chain<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Timer one is <code>system.webServer\/webLimits\/connectionTimeout<\/code>, which defaults to 00:02:00 and applies to idle connections at the HTTP.sys level. Timer two is ARR&#8217;s <code>proxyTimeout<\/code> under <code>system.webServer\/proxy<\/code>, also 120 seconds by default, measured from the last byte relayed. Timer three is whatever your app does to keep the socket warm \u2014 a WebSocket ping, a Socket.IO heartbeat, or nothing at all. For non-WebSocket requests, timer four is the request timeout inside the app itself.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The effective lifetime is not the minimum of the three but the minimum minus jitter: a WebSocket with no application-level ping is closed at 60 or 120 seconds depending on which tier resets first, and any load balancer in front with a 60-second idle timeout wins regardless of what IIS is configured to allow. Always look at the outermost hop first.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why 60 seconds shows up so often<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Sixty seconds is the default idle timeout on Azure Load Balancer and Application Gateway&#8217;s public front end, on many cloud WAFs, and on several managed proxies that sit in front of IIS. It also matches the classic squid and nginx <code>proxy_read_timeout<\/code> default. When a customer reports &#8220;the connection drops at one minute&#8221;, the ARR layer is usually innocent and the front door is the culprit.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The consequence for WebSockets is arithmetic. If the outer idle timeout is 60 seconds and your ping interval is 55, you survive. If the ping interval is 90, you never survive, no matter how generous the ARR settings are. Set the ping interval to roughly half the shortest idle timeout in the chain and the connection becomes stable without changing any proxy configuration at all.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Configuring ARR for long-lived connections<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Two settings do most of the work: raise <code>proxyTimeout<\/code> on the server node so ARR does not cut the stream, and make sure <code>enabled<\/code> is true on the proxy element. In <code>applicationHost.config<\/code>, under <code>&lt;system.webServer&gt;&lt;proxy&gt;<\/code>, set <code>proxyTimeout=\"00:10:00\"<\/code>, <code>timeout=\"00:10:00\"<\/code>, and leave <code>preserveHostHeader=\"true\"<\/code>. On Windows Server, install with <code>Install-WindowsFeature Web-Server,Web-WebSockets,Web-ApplicationRequestRouting -IncludeManagementTools<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Apply with appcmd rather than hand-editing XML: <code>appcmd.exe set config -section:system.webServer\/proxy \/proxyTimeout:\"00:10:00\" \/commit:apphost<\/code>. Restart the W3SVC service or recycle the app pool, because proxy settings are read at worker process start. A common failure mode is changing the site-level web.config, where <code>system.webServer\/proxy<\/code> is locked and the change silently does not apply.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Proving which timer fired with netstat and TIME_WAIT<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Run <code>netstat -ano | findstr :443<\/code> on the ARR server while reproducing the drop. A connection living in <code>ESTABLISHED<\/code> until the exact cutoff and then vanishing with no FIN from the client points at a proxy-side timeout. A flood of <code>TIME_WAIT<\/code> entries on the ARR-to-backend leg, at roughly the ping interval, means the client is reconnecting every cycle and ARR is closing the upstream each time.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Count them to get the reconnect rate: <code>(Get-NetTCPConnection -State TimeWait -RemotePort 8080).Count<\/code>. If that number grows by one every 60 seconds per client, the socket is being re-established on a fixed cadence. IIS logs with <code>cs-host<\/code> and <code>time-taken<\/code> will show the matching requests at 60000-61000 ms, which is the smoking gun.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Recommended values<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Layer<\/th><th>Setting<\/th><th>Default<\/th><th>Recommended<\/th><\/tr><\/thead><tbody><tr><td>Front-end LB<\/td><td>idle timeout<\/td><td>60 s<\/td><td>Raise to 300 s or ping below it<\/td><\/tr><tr><td>ARR<\/td><td>proxyTimeout<\/td><td>120 s<\/td><td>600 s<\/td><\/tr><tr><td>ARR<\/td><td>timeout (connect)<\/td><td>120 s<\/td><td>30 s<\/td><\/tr><tr><td>IIS<\/td><td>webLimits connectionTimeout<\/td><td>120 s<\/td><td>600 s<\/td><\/tr><tr><td>App pool<\/td><td>idleTimeout<\/td><td>20 min<\/td><td>0 (disabled)<\/td><\/tr><tr><td>App<\/td><td>WS ping interval<\/td><td>none<\/td><td>\u2264 half shortest idle<\/td><\/tr><tr><td>App<\/td><td>pong deadline<\/td><td>none<\/td><td>2 \u00d7 ping interval<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Load test before you tune in production<\/h2>\n\n\n<p class=\"wp-block-paragraph\">Reproduce the drop on a staging node with a ping interval of 90 seconds and <code>time-taken<\/code> logging enabled, then confirm the change moves the cutoff from 60 to your new value. Without that measurement you cannot distinguish an idle timeout from a genuine application exception that closes the socket, and the second problem gets masked every time you raise a number.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Keep the smallest set of changes that produces a clean 15-minute idle WebSocket in netstat, then document the chain so the next person knows which timer owns what. If your current VPS cannot hold a long-lived proxy process and log at the same time, the disk and memory limits are worth reviewing on <a href=\"https:\/\/windows-vps.org\/#providers\">the provider comparison table<\/a> before you tune further.<\/p>\n\n\n\n\n<h2 class=\"wp-block-heading\">The idle app pool that kills quiet WebSockets<\/h2>\n\n\n<p class=\"wp-block-paragraph\">Even with every proxy timer raised, a WebSocket-only workload looks idle to IIS. The application pool&#8217;s <code>idleTimeout<\/code> defaults to 20 minutes and recycles the worker even though sockets are open, because managed WebSocket connections do not always count as activity for the idle monitor. Set <code>idleTimeout=\"00:00:00\"<\/code> on the pool in <code>applicationHost.config<\/code>, or via <code>appcmd set apppool \"DefaultAppPool\" \/idleTimeout:00:00:00<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Also disable the periodic recycle if the app tolerates it: <code>Set-ItemProperty IIS:\\AppPools\\DefaultAppPool -Name recycling.periodicRestart.time -Value 0<\/code>. Otherwise the 1,740-minute default recycles the pool every 29 hours and every connected client is dropped at once, on a schedule that looks random until you notice the rhythm.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Load test before you tune in production<\/h2>\n\n\n<p class=\"wp-block-paragraph\">Reproduce the drop on a staging node with a ping interval of 90 seconds and <code>time-taken<\/code> logging enabled, then confirm the change moves the cutoff from 60 to your new value. Without that measurement you cannot distinguish an idle timeout from a genuine application exception that closes the socket, and the second problem gets masked every time you raise a number.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Keep the smallest set of changes that produces a clean 15-minute idle WebSocket in netstat, then document the chain so the next person knows which timer owns what. If your current VPS cannot hold a long-lived proxy process and log at the same time, the disk and memory limits are worth reviewing on <a href=\"https:\/\/windows-vps.org\/#providers\">the provider comparison table<\/a> before you tune further.<\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>An IIS site proxied through Application Request Routing that dies at almost exactly 60 seconds is not a bug in your application. It is the ARR proxy timeout, and the &#8220;exactly&#8221; is the tell: ARR&#8217;s default proxyTimeout is 120 seconds at the server level, but several templates and Azure App Service front ends inherit a &#8230; <a title=\"IIS ARR Keep-Alive and WebSocket Timeouts: Why Proxied Connections Die at 60 Seconds\" class=\"read-more\" href=\"https:\/\/windows-vps.org\/blog\/iis-arr-application-request-routing-websocket-keepalive-timeouts\/\" aria-label=\"Read more about IIS ARR Keep-Alive and WebSocket Timeouts: Why Proxied Connections Die at 60 Seconds\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":1,"footnotes":""},"categories":[5],"tags":[],"class_list":["post-977","post","type-post","status-publish","format-standard","hentry","category-tutorials-guides"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v26.1 (Yoast SEO v26.1) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>IIS ARR Keep-Alive and WebSocket Timeouts: Why Proxied Connections Die at 60 Seconds - Windows VPS Blog<\/title>\n<meta name=\"description\" content=\"An IIS site proxied through Application Request Routing that dies at almost exactly 60 seconds is not a bug in your application. It is the ARR proxy\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/windows-vps.org\/blog\/iis-arr-application-request-routing-websocket-keepalive-timeouts\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"IIS ARR Keep-Alive and WebSocket Timeouts: Why Proxied Connections Die at 60 Seconds\" \/>\n<meta property=\"og:description\" content=\"IIS ARR Keep-Alive and WebSocket Timeouts: Why Proxied Connections Die at 60 Seconds\" \/>\n<meta property=\"og:url\" content=\"https:\/\/windows-vps.org\/blog\/iis-arr-application-request-routing-websocket-keepalive-timeouts\/\" \/>\n<meta property=\"og:site_name\" content=\"Windows VPS Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-30T22:54:08+00:00\" \/>\n<meta name=\"author\" content=\"windows-vps\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@myxiechengxuan\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"windows-vps\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"5 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/windows-vps.org\/blog\/iis-arr-application-request-routing-websocket-keepalive-timeouts\/\",\"url\":\"https:\/\/windows-vps.org\/blog\/iis-arr-application-request-routing-websocket-keepalive-timeouts\/\",\"name\":\"IIS ARR Keep-Alive and WebSocket Timeouts: Why Proxied Connections Die at 60 Seconds - Windows VPS Blog\",\"isPartOf\":{\"@id\":\"https:\/\/windows-vps.org\/blog\/#website\"},\"datePublished\":\"2026-10-30T22:54:08+00:00\",\"author\":{\"@id\":\"https:\/\/windows-vps.org\/blog\/#\/schema\/person\/44caceed916d0db318aa08d5623a7a58\"},\"description\":\"An IIS site proxied through Application Request Routing that dies at almost exactly 60 seconds is not a bug in your application. It is the ARR proxy\",\"breadcrumb\":{\"@id\":\"https:\/\/windows-vps.org\/blog\/iis-arr-application-request-routing-websocket-keepalive-timeouts\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/windows-vps.org\/blog\/iis-arr-application-request-routing-websocket-keepalive-timeouts\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/windows-vps.org\/blog\/iis-arr-application-request-routing-websocket-keepalive-timeouts\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/windows-vps.org\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"IIS ARR Keep-Alive and WebSocket Timeouts: Why Proxied Connections Die at 60 Seconds\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/windows-vps.org\/blog\/#website\",\"url\":\"https:\/\/windows-vps.org\/blog\/\",\"name\":\"Windows VPS Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/windows-vps.org\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/windows-vps.org\/blog\/#\/schema\/person\/44caceed916d0db318aa08d5623a7a58\",\"name\":\"windows-vps\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/windows-vps.org\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/3f2573db5afcd1a6ab9abcc5d48fc8e42584bc87ab9d98cc156e5b2097766dd9?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/3f2573db5afcd1a6ab9abcc5d48fc8e42584bc87ab9d98cc156e5b2097766dd9?s=96&d=mm&r=g\",\"caption\":\"windows-vps\"},\"sameAs\":[\"https:\/\/windows-vps.org\/blog\",\"https:\/\/x.com\/myxiechengxuan\"],\"url\":\"https:\/\/windows-vps.org\/blog\/author\/myxiechengxuan\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"IIS ARR Keep-Alive and WebSocket Timeouts: Why Proxied Connections Die at 60 Seconds - Windows VPS Blog","description":"An IIS site proxied through Application Request Routing that dies at almost exactly 60 seconds is not a bug in your application. It is the ARR proxy","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/windows-vps.org\/blog\/iis-arr-application-request-routing-websocket-keepalive-timeouts\/","og_locale":"en_US","og_type":"article","og_title":"IIS ARR Keep-Alive and WebSocket Timeouts: Why Proxied Connections Die at 60 Seconds","og_description":"IIS ARR Keep-Alive and WebSocket Timeouts: Why Proxied Connections Die at 60 Seconds","og_url":"https:\/\/windows-vps.org\/blog\/iis-arr-application-request-routing-websocket-keepalive-timeouts\/","og_site_name":"Windows VPS Blog","article_published_time":"2026-10-30T22:54:08+00:00","author":"windows-vps","twitter_card":"summary_large_image","twitter_creator":"@myxiechengxuan","twitter_misc":{"Written by":"windows-vps","Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/windows-vps.org\/blog\/iis-arr-application-request-routing-websocket-keepalive-timeouts\/","url":"https:\/\/windows-vps.org\/blog\/iis-arr-application-request-routing-websocket-keepalive-timeouts\/","name":"IIS ARR Keep-Alive and WebSocket Timeouts: Why Proxied Connections Die at 60 Seconds - Windows VPS Blog","isPartOf":{"@id":"https:\/\/windows-vps.org\/blog\/#website"},"datePublished":"2026-10-30T22:54:08+00:00","author":{"@id":"https:\/\/windows-vps.org\/blog\/#\/schema\/person\/44caceed916d0db318aa08d5623a7a58"},"description":"An IIS site proxied through Application Request Routing that dies at almost exactly 60 seconds is not a bug in your application. It is the ARR proxy","breadcrumb":{"@id":"https:\/\/windows-vps.org\/blog\/iis-arr-application-request-routing-websocket-keepalive-timeouts\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/windows-vps.org\/blog\/iis-arr-application-request-routing-websocket-keepalive-timeouts\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/windows-vps.org\/blog\/iis-arr-application-request-routing-websocket-keepalive-timeouts\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/windows-vps.org\/blog\/"},{"@type":"ListItem","position":2,"name":"IIS ARR Keep-Alive and WebSocket Timeouts: Why Proxied Connections Die at 60 Seconds"}]},{"@type":"WebSite","@id":"https:\/\/windows-vps.org\/blog\/#website","url":"https:\/\/windows-vps.org\/blog\/","name":"Windows VPS Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/windows-vps.org\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/windows-vps.org\/blog\/#\/schema\/person\/44caceed916d0db318aa08d5623a7a58","name":"windows-vps","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/windows-vps.org\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/3f2573db5afcd1a6ab9abcc5d48fc8e42584bc87ab9d98cc156e5b2097766dd9?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/3f2573db5afcd1a6ab9abcc5d48fc8e42584bc87ab9d98cc156e5b2097766dd9?s=96&d=mm&r=g","caption":"windows-vps"},"sameAs":["https:\/\/windows-vps.org\/blog","https:\/\/x.com\/myxiechengxuan"],"url":"https:\/\/windows-vps.org\/blog\/author\/myxiechengxuan\/"}]}},"_links":{"self":[{"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/posts\/977","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/comments?post=977"}],"version-history":[{"count":3,"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/posts\/977\/revisions"}],"predecessor-version":[{"id":980,"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/posts\/977\/revisions\/980"}],"wp:attachment":[{"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/media?parent=977"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/categories?post=977"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/tags?post=977"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}