xc
33 TopicsMoving HTTP Load Balancers Between F5 Distributed Cloud Namespaces — Why It's Harder Than You Think
The Problem If you have been working with F5 Distributed Cloud (XC) for a while, you have probably run into this: your namespace structure no longer reflects how your teams or applications are organized. Maybe the initial layout was a quick decision during onboarding. Maybe teams have merged, projects have grown, or your naming convention has evolved. Either way, you now want to move a handful of HTTP load balancers from one namespace to another. Simple enough, right? Just change the namespace field and save... Except you can't. There is no "move" operation on F5 XC - not in the UI, not in the API. Changing the namespace of a load balancer means deleting it in the source and re-creating it in the target. And that is where things get complicated. Why a Simple Delete-and-Recreate Is Not Enough On the surface, the API is straightforward: "GET" the config, "DELETE" the object, "POST" it into the new namespace. But a production HTTP load balancer on XC is rarely a standalone object. It sits at the top of a dependency tree that can include origin pools, health checks, TLS certificates, service policies, app firewalls, rate limiters, and more. Every one of those dependencies needs to be handled correctly - or the migration breaks. Here are the main challenges we might run into. Referential Integrity F5 XC enforces strict referential integrity. You cannot delete an origin pool that is still referenced by a load balancer. You cannot create a load balancer that references an origin pool that does not exist yet. This means the order of operations matters: delete top-down (LBs first, then dependencies), create bottom-up (dependencies first, then LBs). It also means that if two load balancers share an origin pool, you cannot move them independently. Delete the first LB, try to delete the shared pool, and the API returns a 409 Conflict because the second LB still references it. Both LBs - and all of their shared dependencies - have to be moved together as a single atomic unit. New CNAMEs After Every Move When you delete and re-create an HTTP load balancer, F5 XC assigns a new "host_name" (the CNAME target that your DNS records point to). If the LB uses Let's Encrypt auto-certificates, the ACME challenge CNAME changes too. That means after every move, someone needs to update external DNS records - and until that happens, the application is unreachable or the TLS certificate renewal fails. For tenants using XC-managed DNS zones with "Allow Application Loadbalancer Managed Records" enabled, this is handled automatically. But many customers manage their own DNS, and they need the old and new CNAME values for every moved LB. Certificates with Non-Portable Private Keys This one is subtle. When a load balancer uses a manually imported TLS certificate, the private key is stored in one of several formats: blindfolded (encrypted with the Volterra blindfold key) or clear secret. In both of these cases, the XC API never returns the private key material in its GET response. You get the certificate and metadata, but not the key. That means you cannot extract-and-recreate the certificate in a new namespace via the API. Cross-namespace certificate references (outside of "shared" namespace) are also not supported. So if an LB in namespace A uses a manually imported certificate stored in namespace A, and you want to move that LB to namespace B, you need to first manually upload the same certificate into namespace B (or into the "shared" namespace) before the migration can proceed. API Metadata The XC API returns a "referring_objects" field on every config GET response. In theory, this tells you what other objects reference a given resource - exactly what you need to know before deleting something. In practice, this field can be empty even when active references exist. The only reliable way to detect all external references is to actively scan: fetch the config of every load balancer in the namespace and check their specs for references to the objects you are about to move. Cross-Namespace References Are Not Allowed On F5 XC, an HTTP load balancer can only reference objects in its own namespace, in "system" or "shared" namespace. If your origin pool lives in namespace A and you move the LB to namespace B, the origin pool must either come along to namespace B or already exist there. There is no way to have the LB in namespace B point to a pool in namespace A. This means you need to discover the complete transitive dependency tree of every LB, determine which dependencies need to move, detect which are shared between multiple LBs, and batch everything accordingly. The Tool: XC Namespace migration To deal with all of this, (A)I built **xc-ns-mover** — a Python CLI tool that automates the entire process. It has two components: Scanner - scans all namespaces on your tenant, lists every HTTP load balancer, and writes a CSV report. This gives you the inventory to decide what to move. Mover - takes a CSV of load balancers, discovers all dependencies, groups LBs that share dependencies into atomic batches, runs a series of pre-flight checks, and then executes the migration - or generates a dry-run report so you can review everything first, or do the job manually (JSON Code blocks available in the report) What the Mover Does Before Touching Anything The mover runs six pre-flight phases before making any changes: Discovery and batching - fetches every LB config, walks the dependency tree, and uses a union-find algorithm to cluster LBs with shared dependencies into batches. External reference scan - for every dependency being moved, checks whether any LB outside the move list references it. If so, that dependency cannot be moved without breaking the external LB, and the batch is blocked. Conflict detection - lists all existing objects in the target namespace. If a name already exists, the user can skip the object or rename it with a configurable prefix (e.g., "migrated-my-pool"). All internal JSON references are updated automatically. Certificate pre-flight - identifies certificates with non-portable private keys, then searches the target and "shared" namespaces for a matching certificate by domain/SAN comparison (including wildcard matching per RFC 6125). If a match is found, the LB's certificate reference is automatically rewritten. If not, the batch is blocked until the certificate is manually created. DNS zone pre-flight - queries the tenant's DNS zones to detect which ones have managed LB records enabled. LBs under managed zones are flagged as "auto-managed" in the report — no manual DNS update needed. After all checks pass, the actual migration follows a strict order per batch: backup everything, delete top-down, create bottom-up, verify new CNAMEs. If anything fails, automatic rollback kicks in — objects created in the target are deleted, objects deleted from the source are restored from backups. The Reports Every run produces an HTML report. The dry-run report shows planned configurations, the full dependency graph , certificate issues, DNS changes required, and any blocking issues — all before a single API call mutates anything. The post-migration report includes old and new CNAME values, a DNS changes table with exactly which records need updating, and full configuration backups of everything that was touched. Things to Keep in Mind A few caveats that are worth highlighting: Brief interruption is unavoidable - The migration deletes and re-creates load balancers. During that window (typically seconds to a few minutes per batch), traffic to affected domains will be impacted. Plan a change window. Only HTTP load balancers are supported - TCP load balancers and other object types are not handled by this tool. DNS updates are your responsibility - The report gives you all the values - old CNAME, new CNAME, ACME challenge CNAME - but you need to update your DNS provider. Always run the dry-run first - The tool enforces this by default: it stores a fingerprint after a dry-run and verifies it before executing. If the config changes, a new dry-run is required. The project is open source and available on GitHub. This is privately maintained and not "officially supported": https://github.com/de1chk1nd/resources-and-tools/blob/main/tools/xc-ns-mover/README.md If you find bugs or have feature requests, please open a GitHub issue.127Views3likes0CommentsQuestion about healthchecks
Hello, We're publishing quite old server behind XC load balancer, and to make it work I had to lower "TLS Security Level" to Medium under Origin Pool > TLS. This works fine, however, without healthchecks. If I enable a simple healthcheck, for example: Host Header Value = my.hostname Path = / I start getting "503 Service Unavailable" errors. I checked the web server logs on the server and there are no hits, so I suspect the healthcheck uses newer TLS protocols/ciphers, therefore, it fails (as it used to fail when TLS Security Level was set by default to High). Can TLS protocols/ciphers used by the healthecks be configured? And second question, are there any logs that could be enabled for healthchecks? Thank you.Solved156Views0likes4CommentsCORS with API calls
Hello, Sorry if this is an obvious question -- we're very new to XC. We're using XC with one load balancer with CORS activated. It works fine for web applications but all API calls (to our internal APIs) are blocked because of missing origin header. What is the correct way to handle it? Ask the connecting party to insert origin headers? Dedicate another load balancer (to be used for APIs only) with CORS disabled? Thank you.Solved140Views0likes4CommentsSimplifying and Securing Network Segmentation with F5 Distributed Cloud and Nutanix Flow
Introduction Enterprises often separate environments—such as development and production—to improve efficiency, reduce risk, and maintain compliance. A critical enabler of this separation is network segmentation, which isolates networks into smaller, secured segments—strengthening security, optimizing performance, and supporting regulatory standards. In this article, we explore the integration between Nutanix Flow and F5 Distributed Cloud, showcasing how F5 and Nutanix collaborate to simplify and secure network segmentation across diverse environments—on-premises, remote, and hybrid multicloud. Integration Overview At the heart of this integration is the capability to deploy a F5 Distributed Cloud Customer Edge (CE) inside a Nutanix Flow VPC, establish BGP peering with the Nutanix Flow BGP Gateway, and inject CE-advertised BGP routes into the VPC routing table. This architecture provides full control over application delivery and security within the VPC. It enables selective advertisement of HTTP load balancers (LBs) or VIPs to designated VPCs, ensuring secure and efficient connectivity. By leveraging F5 Distributed Cloud to segment and extend networks to remote location—whether on-premises or in the public cloud—combined with Nutanix Flow for microsegmentation within VPCs, enterprises achieve comprehensive end-to-end security. This approach enforces a consistent security posture while reducing complexity across diverse infrastructures. In our previous article (click here) , we explored application delivery and security. Here, we focus on network segmentation and how this integration simplifies connectivity across environments. Demo Walkthrough The demo consists of two parts: Extending a local network segment from a Nutanix Flow VPC to a remote site using F5 Distributed Cloud. Applying microsegmentation within the network segment using Nutanix Flow Security Next-Gen. San Jose (SJ) serves as our local site, and the demo environment dev3 is a Nutanix Flow VPC with an F5 Distributed Cloud Customer Edge (CE) deployed inside: *Note: The SJ CE is named jy-nutanix-overlay-dev3 in the F5 Distributed Cloud Console and xc-ce-dev3 in the Nutanix Prism Central. On the F5 Distributed Cloud Console, we created a network segment named jy-nutanix-sjc-nyc-segment and we assigned it specifically to the subnet 192.170.84.0/24: eBGP peering is ESTABLISHED between the CE and the Nutanix Flow BGP Gateway in this segment: At the remote site in NYC, a CE named jy-nutanix-nyc is deployed with a local subnet of 192.168.60.0/24: To extend jy-nutanix-sjc-nyc-segment from SJ to NYC, simply assign the segment jy-nutanix-sjc-nyc-segment to the NYC CE local subnet 192.168.60.0/24 in the F5 Distributed Cloud Console: Effortlessly and in no time, the segment jy-nutanix-sjc-nyc-segment is now extended across environments from SJ to NYC: Checking the CE routing table, we can see that the local routes originated from the CEs are being exchanged among them: At the local site SJ, the SJ CE jy-nutanix-overlay-dev3 advertises the remote route originating from the NYC CE jy-nutanix-nyc to the Nutanix Flow BGP Gateway via BGP, and installs the route in the dev3 routing table: SJ VMs can now reach NYC VMs and vice versa, while continuing to use their Nutanix Flow VPC logical router as the default gateway: To enforce granular security within the segment, Nutanix Flow Security Next-Gen provides microsegmentation. Together, F5 Distributed Cloud and Nutanix Flow Security Next-Gen deliver a cohesive solution: F5 Distributed cloud seamlessly extends network segments across environments, while Nutanix Flow Security Next-Gen ensures fine-grained security controls within those segments: Our demo extends a network segment between two data centers, but the same approach can also be applied between on-premises and public cloud environments—delivering flexibility across hybrid multicloud environments. Conclusion F5 Distributed Cloud simplifies network segmentation across hybrid and multi-cloud environments, making it both secure and effortless. By seamlessly extending network segments across any environment, F5 removes the complexity traditionally associated with connecting diverse infrastructures. Combined with Nutanix Flow Security Next-Gen for microsegmentation within each segment, this integration delivers end-to-end protection and consistent policy enforcement. Together, F5 and Nutanix help enterprises reduce operational overhead, maintain compliance, and strengthen security—while enabling agility and scalability across all environments. This integration is coming soon in CY2026. If you’re interested in early access, please contact your F5 representative. Related URLs Delivering Secure Application Services Anywhere with Nutanix Flow and F5 Distributed Cloud | DevCentral F5 Distributed Cloud - https://www.f5.com/products/distributed-cloud-services Nutanix Flow Network Security - https://www.nutanix.com/products/flow
240Views2likes0CommentsDoes XC DNS support health monitoring for CNAME records?
Hi everyone, I have a question regarding health monitor with CNAME records in the XC DNS Load Balancer. If I configure a Type A DNS Load Balancer in XC, I can attach a DNS pool with health monitor. However, if I configure a Type CNAME DNS Load Balancer with a CNAME-type pool, I can't select any health monitor for the CNAME pool. Our goal is to monitor a server service hosted in a third-party cloud and avoid the cloud edge service going down. Once the XC DNS detect a service failure, then it will reply with the fallback dns record (from another cloud service) to the user. Is there have any other way to monitor the health of CNAME pool ? Regards, Ding77Views0likes0CommentsHTTP portal with the NTLM auth flow is broken on XC.
We are trying to protect an OWA365 portal with XC, but some requests with NTLM authentication show an Error 503 - Service Unavailable message in XC. I think that is the NTLM auth process because when try the same HTTP GET with "Authorization: Basic" it works fine. curl -v https://autodiscover.example.com/autodiscover/autodiscover.xml -H "Authorization: Basic ZG9tYWluXHVzZXI6UHJ1ZWJhc2RlcGFzc3dvcmQ=" < HTTP/2 200 < cache-control: private < content-type: text/xml; charset=utf-8 < request-id: 00000000-0000-0000-0000-000000000000 < server: volt-adc < <?xml version="1.0" encoding="utf-8"?> <Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006"> <Response> <Error Time="20:00:00.0000000" Id="000000000"> But the browser always fails. First, it responds with a 401 HTTP code. After sending the credentials, XC shows a 503-03 error: 'Service unavailable'. method: GET host: autodiscover.example.com req_path: /autodiscover/autodiscover.xml req_body: — api_endpoint: UNKNOWN scheme: https rsp_code: 503 rsp_code_details: upstream_reset_before_response_started{remote_reset} Do F5-XC have an OWA template or something about the NTLM user portal?Solved168Views0likes3CommentsShow or List F5 XC Routes in the Web
Hi F5ers, After more than two years working with F5 XC, I have decided to explore a functionality to show the host associated with each route "I have requested this functionality to F5, but it´s in design." For anyone who has deployed XC and has created routes into the load balancers, they may have encountered the fact that the routes don't have any description or relevant information, and in the case that they have to find a specific route, it could be almost impossible in an incident, or it will take a lot of time to navigate the menu. So, what I propose as an alternative solution, meanwhile, is F5 solving the request? I have designed a JavaScript that can be integrated into a bookmark "easy way", and if you copy the entire JSON configuration of the load balancer, it will show you in a console over the main XC web page the specific routes and their position in the Routes Menu. The steps to deploy it are: Create a new bookmark and copy the next encoded JavaScript in the URL New Bookmark javascript:(async()=>{const H=h=>{if(!h)return'';const i=h.invert_match?%27NOT %27:%27%27;const n=(h.name||%27%27)+%27%27;if(n.toLowerCase()===%27host%27){if(h.regex)return`${i}Host Regex: ${h.regex}`;if(h.exact)return`${i}Host: ${h.exact}`;if(h.match_value)return`${i}Host: ${h.match_value}`;if(h.value)return`${i}Host: ${h.value}`;if(Array.isArray(h.values)&&h.values.length)return`${i}Host in [${h.values.join(%27 | %27)}]`;return`${i}Host Header Present`}if(h.regex)return`${i}Header Regex: ${n} ~ ${h.regex}`;if(h.exact)return`${i}Header: ${n} = ${h.exact}`;if(h.match_value)return`${i}Header: ${n} = ${h.match_value}`;if(h.value)return`${i}Header: ${n} = ${h.value}`;if(Array.isArray(h.values)&&h.values.length)return`${i}Header: ${n} in [${h.values.join(%27 | %27)}]`;return`${i}Header: ${n} (present)`},S=t=>{try{let s=t.replace(/^\uFEFF/,%27%27).replace(/\u200B/g,%27%27);s=s.replace(/\/\*[^]*?\*\//g,%27%27);s=s.replace(/(^|[^:])\/\/.*$/gm,%27$1%27);s=s.replace(/,\s*([}\]])/g,%27$1%27);return s}catch{return t}},J=t=>{if(!t)return null;try{return JSON.parse(t)}catch{try{return JSON.parse(S(t))}catch{return null}}},G=()=>{try{return(getSelection()?.toString()||%27%27).trim()}catch{return%27%27}},D=()=>{const o=[];document.querySelectorAll(%27pre,code,textarea,div%27).forEach(el=>{const t=(el.innerText||el.textContent||%27%27).trim();if(t&&t.includes(%27"spec"%27)&&t.includes(%27"routes"%27)&&t.includes(%27"metadata"%27))o.push(t)});return o},P=a=>{for(const r of a){let t=r,i=t.indexOf(%27{%27),j=t.lastIndexOf(%27}%27);if(i>=0&&j>i)t=t.slice(i,j+1);const x=J(t);if(x?.spec?.routes)return x}return null},M=()=>{try{if(window.monaco?.editor?.getModels){for(const m of window.monaco.editor.getModels()){const txt=m.getValue?.();const j=J(txt);if(j?.spec?.routes)return j}}}catch{}return null},Q=onOk=>{const host=document.createElement(%27div%27),shadow=host.attachShadow({mode:%27open%27}),ov=document.createElement(%27div%27);ov.style.cssText=%27position:fixed;inset:0;z-index:1000000;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;outline:none;%27;ov.tabIndex=0;const box=document.createElement(%27div%27);box.style.cssText=%27width:min(960px,92vw);height:min(76vh,720px);background:#111;color:#eee;border:1px solid #444;border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,.35);display:flex;flex-direction:column';const head=document.createElement('div');head.style.cssText='padding:10px 12px;border-bottom:1px solid #333;font:600 14px system-ui';head.textContent='Pega o carga el JSON del HTTP LB (vista JSON)';const bar=document.createElement('div');bar.style.cssText='display:flex;gap:8px;align-items:center;padding:8px 12px;border-bottom:1px solid #333';const btnRead=document.createElement('button');btnRead.textContent='📋 Leer portapapeles';btnRead.title='Requiere permiso del navegador';btnRead.style.cssText='background:#2b2b2b;color:#ddd;border:1px solid #444;border-radius:6px;padding:6px 10px;cursor:pointer';btnRead.onclick=async()=>{try{const txt=await navigator.clipboard.readText();ta.value=txt;ta.focus()}catch{alert('No se pudo leer del portapapeles. Permite el permiso o usa Archivo.')}};const file=document.createElement('input');file.type='file';file.accept='.json,.txt,application/json,text/plain';file.style.cssText='color:#bbb';file.onchange=async e=>{const f=e.target.files?.[0];if(!f)return;const txt=await f.text();ta.value=txt;ta.focus()};const tip=document.createElement('div');tip.style.cssText='margin-left:auto;color:#aaa;font-size:12px';tip.textContent='Consejo: arrastra y suelta un archivo aquí';bar.append(btnRead,file,tip);const ta=document.createElement('textarea');ta.style.cssText='flex:1;padding:10px 12px;background:#0f0f0f;color:#eee;border:0;outline:none;resize:none;font:12px/1.4 ui-monospace,Menlo,Consolas,monospace';ta.placeholder='Pega aquí el JSON (Ctrl+V). Si la página intercepta, usa "Leer portapapeles" o Archivo.';const pasteToTa=async e=>{try{let d=e.clipboardData?.getData('text/plain');if(!d&&navigator.clipboard?.readText)d=await navigator.clipboard.readText();if(typeof d==='string'){const st=ta.selectionStart??ta.value.length,en=ta.selectionEnd??ta.value.length;ta.value=ta.value.slice(0,st)+d+ta.value.slice(en);const pos=st+d.length;ta.setSelectionRange(pos,pos);ta.focus()}}catch{}};const globalPaste=e=>{e.stopImmediatePropagation?.();e.stopPropagation();e.preventDefault();pasteToTa(e)};window.addEventListener('paste',globalPaste,true);ta.addEventListener('dragover',e=>{e.preventDefault();ta.style.outline='1px dashed #555'});ta.addEventListener('dragleave',()=>ta.style.outline='');ta.addEventListener('drop',async e=>{e.preventDefault();ta.style.outline='';const f=e.dataTransfer.files?.[0];if(f)ta.value=await f.text()});const foot=document.createElement('div');foot.style.cssText='display:flex;gap:10px;justify-content:flex-end;padding:10px 12px;border-top:1px solid #333';const ok=document.createElement('button');ok.textContent='Validar y mostrar';ok.style.cssText='background:#2b2b2b;color:#ddd;border:1px solid #444;border-radius:6px;padding:6px 12px;cursor:pointer';ok.onclick=()=>{const j=J(ta.value);if(!(j?.spec?.routes)){alert('No parece un JSON válido con spec.routes.\nAsegúrate de copiar la vista JSON completa.');return}cleanup();onOk(j)};const cancel=document.createElement('button');cancel.textContent='Cancelar';cancel.style.cssText='background:#222;color:#bbb;border:1px solid #444;border-radius:6px;padding:6px 12px;cursor:pointer';const cleanup=()=>{try{window.removeEventListener('paste',globalPaste,true)}catch{}host.remove()};cancel.onclick=cleanup;foot.append(ok,cancel);box.append(head,bar,ta,foot);ov.append(box);shadow.append(ov);document.body.append(host);setTimeout(()=>ta.focus(),0);ov.addEventListener('mousedown',()=>ta.focus())},A=()=>{const s=G();let j=J(s);if(j?.spec?.routes)return Promise.resolve(j);j=M();if(j?.spec?.routes)return Promise.resolve(j);const hits=D();j=P(hits);if(j?.spec?.routes)return Promise.resolve(j);return new Promise(res=>Q(res))},R=jobj=>{const routes=jobj?.spec?.routes||[],id='xcHostMatchesPanel';document.getElementById(id)?.remove();const panel=document.createElement('div');panel.id=id;panel.style.cssText=['position:fixed','z-index:999999','top:12px','left:12px','max-width:560px','max-height:75vh','overflow:auto','background:#111','color:#eee','border:1px solid #444','border-radius:8px','font:13px/1.35 system-ui,Segoe UI,Roboto,Arial','padding:0','box-shadow:0 8px 24px rgba(0,0,0,.35)','cursor:grab'].join(';');const header=document.createElement('div');header.style.cssText='user-select:none;background:#1b1b1b;border-bottom:1px solid #333;border-top-left-radius:8px;border-top-right-radius:8px;padding:8px 12px;position:relative';header.innerHTML='<div style="font-weight:600">F5 XC — Host match (sin API)</div><div style="opacity:.8;font-size:12px">Fuente: selección/DOM/portapapeles/archivo</div>';const close=document.createElement('button');close.textContent='×';close.title='Cerrar';close.style.cssText='position:absolute;top:6px;right:8px;background:#333;color:#ddd;border:0;border-radius:4px;padding:2px 6px;cursor:pointer';close.addEventListener('pointerdown',e=>{e.stopPropagation();e.preventDefault()});close.addEventListener('click',e=>{e.stopPropagation();e.preventDefault();cleanup()});header.appendChild(close);panel.appendChild(header);const body=document.createElement('div');body.style.cssText='padding:10px 12px 8px';const hr=()=>{const x=document.createElement('div');x.style.cssText='height:1px;background:#333;margin:8px 0';body.appendChild(x)};if(!routes.length){body.append('Sin routes en el JSON.')}else{routes.forEach((r,i)=>{const idx=i+1,s=r.simple_route||{},rd=r.redirect_route||{};let host='';const others=[];(s.headers||[]).forEach(h=>{const t=H(h);((h.name||'').toLowerCase()==='host')?(host=host||t):others.push(t)});(rd.headers||[]).forEach(h=>{const t=H(h);((h.name||'').toLowerCase()==='host')?(host=host||t):others.push(t)});const path=s.path?(s.path.prefix?%60Path Match: ${s.path.prefix}%60:(s.path.regex?%60Path Regex: ${s.path.regex}%60:'')):(rd.path&&rd.path.prefix?%60Path Match: ${rd.path.prefix}%60:'');const type=s?'Simple Route':(rd?'Redirect Route':'(otro)');const block=document.createElement('div');block.style.marginBottom='8px';block.innerHTML=%60<div style="color:#8bd;">#${idx} — ${type}</div>%60+(host?%60<div>• ${host}</div>%60:'<div>• (sin Host)</div>')+(path?%60<div>• ${path}</div>%60:'')+(others.length?%60<div>• ${others.join('<br>• ')}</div>%60:'');body.appendChild(block);hr()})}const foot=document.createElement('div');foot.style.cssText='display:flex;gap:8px;align-items:center;justify-content:space-between';const left=document.createElement('div');left.style.cssText='display:flex;gap:8px;align-items:center';const reset=document.createElement('button');reset.textContent='Reset posición';reset.style.cssText='background:#2b2b2b;color:#ddd;border:1px solid #444;border-radius:4px;padding:4px 8px;cursor:pointer';reset.onclick=()=>{panel.style.left='12px';panel.style.top='12px';panel.style.right='auto';localStorage.removeItem('XC_PANEL_POS')};left.appendChild(reset);foot.appendChild(left);body.appendChild(foot);panel.appendChild(body);document.body.appendChild(panel);const clamp=(v,min,max)=>Math.max(min,Math.min(max,v)),restore=()=>{try{const pos=JSON.parse(localStorage.getItem('XC_PANEL_POS')||'null');if(pos&&typeof pos.left==='number'&&typeof pos.top==='number'){panel.style.left=pos.left+'px';panel.style.top=pos.top+'px';panel.style.right='auto'}}catch{}},save=()=>{try{const r=panel.getBoundingClientRect();localStorage.setItem('XC_PANEL_POS',JSON.stringify({left:Math.round(r.left),top:Math.round(r.top)}))}catch{}};restore();let drag=false,sx=0,sy=0,sl=0,st=0;function onKey(e){if(e.key==='Escape')cleanup()}function cleanup(){try{window.removeEventListener('keydown',onKey)}catch{}panel.remove()}panel.addEventListener('pointerdown',e=>{if(e.button!==0)return;if(e.target.closest("button, a, input, textarea, select, [draggable='true']"))return;drag=true;panel.setPointerCapture(e.pointerId);sx=e.clientX;sy=e.clientY;const r=panel.getBoundingClientRect();sl=r.left;st=r.top;panel.style.willChange='left, top';panel.style.transition='none';panel.style.cursor='grabbing'});panel.addEventListener('pointermove',e=>{if(!drag)return;const dx=e.clientX-sx,dy=e.clientY-sy,w=panel.offsetWidth,h=panel.offsetHeight,maxL=innerWidth-w-6,maxT=innerHeight-h-6,newL=clamp(sl+dx,6,Math.max(6,maxL)),newT=clamp(st+dy,6,Math.max(6,maxT));panel.style.left=newL+'px';panel.style.top=newT+'px';panel.style.right='auto'});panel.addEventListener('pointerup',e=>{if(!drag)return;drag=false;panel.releasePointerCapture(e.pointerId);panel.style.willChange='';panel.style.cursor='grab';save()});window.addEventListener('resize',()=>{save();restore()});window.addEventListener('keydown',onKey)};try{const json=await A();R(json)}catch(e){console.error(e);alert('No fue posible obtener el JSON. Abre la vista JSON del LB o usa el cuadro para pegar/cargar.')}})(); If you want to explore the JavaScript code, I will leave it at the end of the publication. How does it work? Copy or upload the JSON code of the load balancer In the XC web menu, execute the bookmark and copy the JSON code, and then click on validate and show. It shows you the specific routes and number position for each route, giving the possibility to find the required route easily and quickly. Hope it works for anyone who has the same problem as me. The JavaScript code is: (async () => { /** * F5 XC Host Match Viewer (sin API) — blindado contra listeners externos * - Fuentes: Selección | Monaco | DOM | Cuadro (Pegar / Portapapeles / Archivo) * - Intercepción GLOBAL de 'paste' (captura) mientras el cuadro está abierto: * redirige el contenido al <textarea> propio y corta la propagación/defecto. * - Panel arrastrable, ESC/× para cerrar, posición persistente. */ // ---------- Utils ---------- const formatHeader = (h) => { if (!h) return ''; const inv = h.invert_match ? 'NOT ' : ''; const name = (h.name || '').toString(); if (name.toLowerCase() === 'host') { if (h.regex) return `${inv}Host Regex: ${h.regex}`; if (h.exact) return `${inv}Host: ${h.exact}`; if (h.match_value) return `${inv}Host: ${h.match_value}`; if (h.value) return `${inv}Host: ${h.value}`; if (Array.isArray(h.values) && h.values.length) { return `${inv}Host in [${h.values.join(' | ')}]`; } return `${inv}Host Header Present`; } if (h.regex) return `${inv}Header Regex: ${name} ~ ${h.regex}`; if (h.exact) return `${inv}Header: ${name} = ${h.exact}`; if (h.match_value) return `${inv}Header: ${name} = ${h.match_value}`; if (h.value) return `${inv}Header: ${name} = ${h.value}`; if (Array.isArray(h.values) && h.values.length) { return `${inv}Header: ${name} in [${h.values.join(' | ')}]`; } return `${inv}Header: ${name} (present)`; }; const sanitizeJson = (text) => { try { let s = text.replace(/^\uFEFF/, '').replace(/\u200B/g, ''); s = s.replace(/\/\*[^]*?\*\//g, ''); // /* ... */ s = s.replace(/(^|[^:])\/\/.*$/gm, '$1'); // // ... (evita http://) s = s.replace(/,\s*([}\]])/g, '$1'); // comas colgantes return s; } catch { return text; } }; const tryParseJson = (text) => { if (!text) return null; try { return JSON.parse(text); } catch { try { return JSON.parse(sanitizeJson(text)); } catch { return null; } } }; const getSelectionText = () => { try { return (window.getSelection()?.toString() || '').trim(); } catch { return ''; } }; const findDomCandidates = () => { const out = []; document.querySelectorAll('pre,code,textarea,div').forEach(el => { const t = (el.innerText || el.textContent || '').trim(); if (t && t.includes('"spec"') && t.includes('"routes"') && t.includes('"metadata"')) out.push(t); }); return out; }; const parseFirstJson = (texts) => { for (const raw of texts) { let t = raw; const i = t.indexOf('{'), j = t.lastIndexOf('}'); if (i >= 0 && j > i) t = t.slice(i, j + 1); const jn = tryParseJson(t); if (jn?.spec?.routes) return jn; } return null; }; const tryMonacoModels = () => { try { if (window.monaco?.editor?.getModels) { for (const m of window.monaco.editor.getModels()) { const txt = m.getValue?.(); const j = tryParseJson(txt); if (j?.spec?.routes) return j; } } } catch {} return null; }; // ---------- Cuadro Pegar/Archivo con Shadow DOM + PASTE GLOBAL ---------- let modalState = { open: false, ta: null, host: null, removeGlobal: null }; const showPasteOrFileModal = (onOk) => { // Shadow host para aislar el cuadro const host = document.createElement('div'); const shadow = host.attachShadow({ mode: 'open' }); // Overlay clicable (lleva el foco al textarea) const ov = document.createElement('div'); ov.style.cssText = 'position:fixed;inset:0;z-index:1000000;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;outline:none;'; ov.tabIndex = 0; // para recibir foco ov.addEventListener('mousedown', () => ta?.focus()); const box = document.createElement('div'); box.style.cssText = 'width:min(960px,92vw);height:min(76vh,720px);background:#111;color:#eee;border:1px solid #444;border-radius:10px;' + 'box-shadow:0 8px 24px rgba(0,0,0,.35);display:flex;flex-direction:column'; const head = document.createElement('div'); head.style.cssText = 'padding:10px 12px;border-bottom:1px solid #333;font:600 14px system-ui'; head.textContent = 'Pega o carga el JSON del HTTP LB (vista JSON)'; const bar = document.createElement('div'); bar.style.cssText = 'display:flex;gap:8px;align-items:center;padding:8px 12px;border-bottom:1px solid #333'; const btnRead = document.createElement('button'); btnRead.textContent = '📋 Leer portapapeles'; btnRead.title = 'Requiere permiso del navegador'; btnRead.style.cssText = 'background:#2b2b2b;color:#ddd;border:1px solid #444;border-radius:6px;padding:6px 10px;cursor:pointer'; btnRead.onclick = async () => { try { const txt = await navigator.clipboard.readText(); ta.value = txt; ta.focus(); } catch { alert('No se pudo leer del portapapeles. Permite el permiso o usa Archivo.'); } }; const file = document.createElement('input'); file.type = 'file'; file.accept = '.json,.txt,application/json,text/plain'; file.style.cssText = 'color:#bbb'; file.onchange = async (e) => { const f = e.target.files?.[0]; if (!f) return; const txt = await f.text(); ta.value = txt; ta.focus(); }; const tip = document.createElement('div'); tip.style.cssText = 'margin-left:auto;color:#aaa;font-size:12px'; tip.textContent = 'Consejo: arrastra y suelta un archivo aquí'; bar.append(btnRead, file, tip); const ta = document.createElement('textarea'); ta.style.cssText = 'flex:1;padding:10px 12px;background:#0f0f0f;color:#eee;border:0;outline:none;resize:none;font:12px/1.4 ui-monospace,Menlo,Consolas,monospace'; ta.placeholder = 'Pega aquí el JSON (Ctrl+V). Si la página intercepta, usa "Leer portapapeles" o Archivo.'; // Pegar “blindado” en el <textarea> const pasteToTa = async (e) => { try { let data = e.clipboardData?.getData('text/plain'); if (!data && navigator.clipboard?.readText) { // Fallback si el navegador no expone clipboardData al evento data = await navigator.clipboard.readText(); } if (typeof data === 'string') { const start = ta.selectionStart ?? ta.value.length; const end = ta.selectionEnd ?? ta.value.length; ta.value = ta.value.slice(0, start) + data + ta.value.slice(end); const pos = start + data.length; ta.setSelectionRange(pos, pos); ta.focus(); } } catch {} }; // Interceptor GLOBAL (captura) — redirige SIEMPRE el paste al <textarea> const globalPasteCapture = (e) => { if (!modalState.open) return; e.stopImmediatePropagation?.(); e.stopPropagation(); e.preventDefault(); pasteToTa(e); }; window.addEventListener('paste', globalPasteCapture, true); // Drag&drop de archivo al <textarea> ta.addEventListener('dragover', e => { e.preventDefault(); ta.style.outline = '1px dashed #555'; }); ta.addEventListener('dragleave', () => { ta.style.outline = ''; }); ta.addEventListener('drop', async e => { e.preventDefault(); ta.style.outline = ''; const f = e.dataTransfer.files?.[0]; if (f) ta.value = await f.text(); }); const foot = document.createElement('div'); foot.style.cssText = 'display:flex;gap:10px;justify-content:flex-end;padding:10px 12px;border-top:1px solid #333'; const ok = document.createElement('button'); ok.textContent = 'Validar y mostrar'; ok.style.cssText = 'background:#2b2b2b;color:#ddd;border:1px solid #444;border-radius:6px;padding:6px 12px;cursor:pointer'; ok.onclick = () => { const j = tryParseJson(ta.value); if (!(j?.spec?.routes)) { alert('No parece un JSON válido con spec.routes.\nAsegúrate de copiar la vista JSON completa.'); return; } cleanup(); onOk(j); }; const cancel = document.createElement('button'); cancel.textContent = 'Cancelar'; cancel.style.cssText = 'background:#222;color:#bbb;border:1px solid #444;border-radius:6px;padding:6px 12px;cursor:pointer'; const cleanup = () => { try { window.removeEventListener('paste', globalPasteCapture, true); } catch {} modalState = { open: false, ta: null, host: null, removeGlobal: null }; host.remove(); }; cancel.onclick = cleanup; foot.append(ok, cancel); box.append(head, bar, ta, foot); ov.append(box); shadow.append(ov); document.body.append(host); // Estado global del modal modalState = { open: true, ta, host, removeGlobal: () => window.removeEventListener('paste', globalPasteCapture, true) }; // Foco inicial y al pulsar en overlay setTimeout(() => { ta.focus(); }, 0); ov.addEventListener('click', (ev) => { // Si clic fuera de controles, mueve foco al textarea if (ev.target === ov) ta.focus(); }); }; // ---------- Flujo de adquisición ---------- const acquireJson = () => { const sel = getSelectionText(); let j = tryParseJson(sel); if (j?.spec?.routes) return Promise.resolve(j); j = tryMonacoModels(); if (j?.spec?.routes) return Promise.resolve(j); const hits = findDomCandidates(); j = parseFirstJson(hits); if (j?.spec?.routes) return Promise.resolve(j); return new Promise(res => showPasteOrFileModal(res)); }; // ---------- Panel ---------- const drawPanel = (jobj) => { const routes = jobj?.spec?.routes || []; const id = 'xcHostMatchesPanel'; document.getElementById(id)?.remove(); const panel = document.createElement('div'); panel.id = id; panel.style.cssText = [ 'position:fixed','z-index:999999','top:12px','left:12px', 'max-width:560px','max-height:75vh','overflow:auto', 'background:#111','color:#eee','border:1px solid #444','border-radius:8px', 'font:13px/1.35 system-ui,Segoe UI,Roboto,Arial','padding:0', 'box-shadow:0 8px 24px rgba(0,0,0,.35)','cursor:grab' ].join(';'); const header = document.createElement('div'); header.style.cssText = 'user-select:none;background:#1b1b1b;border-bottom:1px solid #333;border-top-left-radius:8px;border-top-right-radius:8px;padding:8px 12px;position:relative'; header.innerHTML = ` <div style="font-weight:600">F5 XC — Host match (sin API)</div> <div style="opacity:.8;font-size:12px">Fuente: selección/DOM/portapapeles/archivo</div> `; const close = document.createElement('button'); close.textContent = '×'; close.title = 'Cerrar'; close.style.cssText = 'position:absolute;top:6px;right:8px;background:#333;color:#ddd;border:0;border-radius:4px;padding:2px 6px;cursor:pointer'; close.addEventListener('pointerdown', (e) => { e.stopPropagation(); e.preventDefault(); }); close.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); cleanup(); }); header.appendChild(close); panel.appendChild(header); const body = document.createElement('div'); body.style.cssText = 'padding:10px 12px 8px'; const hr = () => { const x = document.createElement('div'); x.style.cssText = 'height:1px;background:#333;margin:8px 0'; body.appendChild(x); }; if (!routes.length) { body.append('Sin routes en el JSON.'); } else { routes.forEach((r, i) => { const idx = i + 1; const s = r.simple_route || {}; const rd = r.redirect_route || {}; let hostLine = ''; const others = []; (s.headers || []).forEach(h => { const t = formatHeader(h); ((h.name || '').toLowerCase() === 'host') ? (hostLine = hostLine || t) : others.push(t); }); (rd.headers || []).forEach(h => { const t = formatHeader(h); ((h.name || '').toLowerCase() === 'host') ? (hostLine = hostLine || t) : others.push(t); }); const path = s.path ? (s.path.prefix ? `Path Match: ${s.path.prefix}` : (s.path.regex ? `Path Regex: ${s.path.regex}` : '')) : (rd.path && rd.path.prefix ? `Path Match: ${rd.path.prefix}` : ''); const type = s ? 'Simple Route' : (rd ? 'Redirect Route' : '(otro)'); const block = document.createElement('div'); block.style.marginBottom = '8px'; block.innerHTML = `<div style="color:#8bd;">#${idx} — ${type}</div>` + (hostLine ? `<div>• ${hostLine}</div>` : '<div>• (sin Host)</div>') + (path ? `<div>• ${path}</div>` : '') + (others.length ? `<div>• ${others.join('<br>• ')}</div>` : ''); body.appendChild(block); hr(); }); } const foot = document.createElement('div'); foot.style.cssText = 'display:flex;gap:8px;align-items:center;justify-content:space-between'; const left = document.createElement('div'); left.style.cssText = 'display:flex;gap:8px;align-items:center'; const reset = document.createElement('button'); reset.textContent = 'Reset posición'; reset.style.cssText = 'background:#2b2b2b;color:#ddd;border:1px solid #444;border-radius:4px;padding:4px 8px;cursor:pointer'; reset.onclick = () => { panel.style.left = '12px'; panel.style.top = '12px'; panel.style.right = 'auto'; localStorage.removeItem('XC_PANEL_POS'); }; left.appendChild(reset); foot.appendChild(left); body.appendChild(foot); panel.appendChild(body); document.body.appendChild(panel); // ---- Drag & persistencia ---- const clamp = (v, min, max) => Math.max(min, Math.min(max, v)); const restore = () => { try { const pos = JSON.parse(localStorage.getItem('XC_PANEL_POS') || 'null'); if (pos && typeof pos.left === 'number' && typeof pos.top === 'number') { panel.style.left = pos.left + 'px'; panel.style.top = pos.top + 'px'; panel.style.right = 'auto'; } } catch {} }; const save = () => { try { const r = panel.getBoundingClientRect(); localStorage.setItem('XC_PANEL_POS', JSON.stringify({ left: Math.round(r.left), top : Math.round(r.top), })); } catch {} }; restore(); let dragging = false, sx = 0, sy = 0, sl = 0, st = 0; function onKey(ev) { if (ev.key === 'Escape') cleanup(); } window.addEventListener('keydown', onKey); function cleanup() { try { window.removeEventListener('keydown', onKey); } catch {} panel.remove(); } panel.addEventListener('pointerdown', (e) => { if (e.button !== 0) return; if (e.target.closest("button, a, input, textarea, select, [draggable='true']")) return; dragging = true; panel.setPointerCapture(e.pointerId); sx = e.clientX; sy = e.clientY; const r = panel.getBoundingClientRect(); sl = r.left; st = r.top; panel.style.willChange = 'left, top'; panel.style.transition = 'none'; panel.style.cursor = 'grabbing'; }); panel.addEventListener('pointermove', (e) => { if (!dragging) return; const dx = e.clientX - sx; const dy = e.clientY - sy; const w = panel.offsetWidth; const h = panel.offsetHeight; const maxLeft = innerWidth - w - 6; const maxTop = innerHeight - h - 6; const newLeft = clamp(sl + dx, 6, Math.max(6, maxLeft)); const newTop = clamp(st + dy, 6, Math.max(6, maxTop)); panel.style.left = newLeft + 'px'; panel.style.top = newTop + 'px'; panel.style.right = 'auto'; }); panel.addEventListener('pointerup', (e) => { if (!dragging) return; dragging = false; panel.releasePointerCapture(e.pointerId); panel.style.willChange = ''; panel.style.cursor = 'grab'; save(); }); window.addEventListener('resize', () => { save(); restore(); }); }; // ---------- Ejecuta ---------- try { const json = await (async () => { const sel = getSelectionText(); let j = tryParseJson(sel); if (j?.spec?.routes) return j; j = tryMonacoModels(); if (j?.spec?.routes) return j; const hits = findDomCandidates(); j = parseFirstJson(hits); if (j?.spec?.routes) return j; return await new Promise(res => showPasteOrFileModal(res)); })(); drawPanel(json); } catch (e) { console.error(e); alert('No fue posible obtener el JSON. Abre la vista JSON del LB o usa el cuadro para pegar/cargar.'); } })();50Views1like1CommentF5 Distributed Cloud (XC) Custom Routes: Capabilities, Limitations, and Key Design Considerations
This article explores how Custom Routes work in F5 Distributed Cloud (XC), why they differ architecturally from standard Load Balancer routes, and what to watch out for in real-world deployments, covering backend abstraction, Endpoint/Cluster dependencies, and critical TLS trust and Root CA requirements.458Views2likes1CommentThinking Outside the Box: Rewriting Web Pages with F5 Distributed Cloud (XC)
This article demonstrates how to dynamically rewrite web page content, such as updating links or replacing text, by using native features in F5 Distributed Cloud (XC). It provides a creative workaround that leverages JavaScript injection to modify pages on the fly, avoiding the need for a separate proxy like NGINX or BIG-IP.698Views4likes3CommentsF5 XC HTTP 404 rout_not_found / rsp_code 404
I would like to add more point about the HTTP 404 error: route_not_found / rsp_code 404 in an XC (RE + CE) deployment. 1. Even if XC has the correct host match value in the route, you might still observe a 404 response. In such cases, check the DNS configuration on the CEs. A possible reason could be that the CEs are unable to resolve DNS for host which is configured in route. 2. Even if XC has the correct host match value, the path might not match. For example, if you have a single route as shown below and the request comes as https://example.com/, you may see rsp_code 404 , as it is not matching any routes. Example : HTTP Method:ANY Path Match : Prefix Prefix:/hello Headers Host example.com Orginpool: example_orgin pool https://my.f5.com/manage/s/article/K000147490137Views1like2Comments