A reverse proxy often has to change what it carries. The internal server calls itself by a name the public should never see, a redirect points at the wrong host, a page links to plain HTTP. BIG-IP offers four ways to change content in flight, from the most declarative to the most manual: a Rewrite profile, an HTML profile, a Stream profile, and iRules. Each is a profile or a rule on the virtual server, so the profiles article is the place to start if the stack is new to you.

The Rewrite profile: URI translation

F5's chapter describes the problem the Rewrite profile solves: when the BIG-IP sends traffic for a public site to an internal server, the server may its own host name, or send redirects that carry its internal host name and path (configuring the BIG-IP system as a reverse proxy server). A Rewrite profile holds URI rules. A URI rule translates the scheme, host, port or path of a client URI, a server URI or both, and it also translates the domain and path in a response's Set-Cookie header when they match the rule. The virtual server needs an HTTP profile, and for HTTPS the TLS must terminate on the BIG-IP.

Which rule applies is decided by specificity, not by the order you typed:

  • a request does not have to match any entry, and with no match and no catch-all the profile has no effect;
  • each request matches one entry only, the one with the most specific host and path;
  • requests whose URIs contain a host name are matched before those that do not;
  • entries are processed from most specific to least specific, regardless of their order in the profile.

The profile rewrites HTML and CSS content only. HTML is recognised by the types text/html and text/xhtml unless an HTML profile says otherwise, and CSS by text/css alone. In tmsh the same profile type also serves the Access portal, so check that rewrite-mode is uri-translation (tmsh reference: ltm profile rewrite).

The HTML profile: rules on tags

An HTML profile lets the BIG-IP modify HTML that passes through it, for example by removing every img tag that has a src attribute, or by matching a tag and attribute and raising an iRule event so a rule can act on the match. The same chapter describes both uses. From an iRule, HTML::tag append inserts a string after the closing delimiter of the matched tag and HTML::tag prepend inserts one before the opening delimiter (HTML::tag).

The Stream profile: search and replace on the stream

A Stream profile searches and replaces strings within a data stream such as a TCP connection, with a source string to find and a target to put in its place (tmsh reference: ltm profile stream). An iRule can set the expression per connection with STREAM::expression, in the form @match@replacement@, where the match value is a kind of regular expression, and the change applies to that connection only, never to the stored profile (STREAM::expression). Three properties decide whether a replacement actually happens.

  • Compression. F5's own example disables compression by the server, because LTM does not uncompress response content and the strings would otherwise be compressed into binary gibberish. The example removes the request's Accept-Encoding header so the server answers uncompressed.
  • Encoding. By default the stream's octets are read as single-byte ISO-8859-1 characters; STREAM::encoding utf-8 makes TMOS transcode them from first. In the default mode the octets 0xC3 0xBC match as two characters; in UTF-8 mode they are one (STREAM::encoding). And STREAM::replace encodes its string in UTF-8, so the replacement can be more octets than characters (STREAM::replace).
  • Chunking. With chunking enabled, the stream processor parses data in chunks, which restricts the extra memory it needs and also limits the longest value it can match to the chunk size; chunking is disabled by default.

On an full-proxy virtual server, the one with the HTTP MRF Router, a Stream profile was rejected until Bug 2251517 was fixed in 21.1.0; the HTTP/2 full-proxy article lists it with the other records on that path.

iRules: headers and payloads by hand

When no profile fits, an iRule edits the message itself. HTTP::header replace replaces the value of the last occurrence of a header and inserts the header if it was absent; when a header appears several times, only the last instance changes (HTTP::header). For the body, HTTP::collect gathers an amount of body data and then raises HTTP_REQUEST_DATA or HTTP_RESPONSE_DATA, where HTTP::payload reads it (HTTP::collect). If several iRules on the same virtual server collect, the last collect wins and the earlier ones are ignored, which F5 calls an unsupported operation to avoid. HTTP::payload replace replaces a span of content and adjusts the Content-Length header, but it treats its string as a byte array, so a UTF-8 string with multibyte characters needs preparing first (HTTP::payload).

That last point is where character counts and byte counts part. UTF-8 encodes a character in one to four octets depending on its number: one for U+0000 to U+007F, two up to U+07FF, three up to U+FFFF, four beyond (RFC 3629). Content-Length counts octets (RFC 9110), and so do collect lengths and payload offsets. "Hello" is five characters and five octets; "café" is four characters and five octets, and "€100" is four characters and six octets. A rule that measures in characters and replaces in bytes cuts a character in half. The Unicode inspector shows the octets of any string, and the character encoding article explains why they differ.

Memory follows the same arithmetic. A collect holds the collected body for each connection that triggers it, so an upper bound on what a rule holds at once is the collect length multiplied by the number of connections collecting at the same moment: 8 KiB collected on 1,000 concurrent connections is about 7.8 MiB, and 64 KiB on 2,000 is 125 MiB. The lab below measures any text both ways, finds the byte offset an edit needs, and works out that bound.

Interactive lab

Find the byte offset a payload edit needs

Type a body and a piece of it to find, and set how much an iRule collects on how many connections at once. The lab counts the text in characters and in UTF-8 octets, places the piece both ways, and bounds the memory the collect can hold.

What happens

Characters (code points)
12
UTF-8 octets
12
Where the piece starts
character 7, byte 7 (counting from 0)
Length of the piece
5 characters, 5 octets
Collect memory, upper bound
7.81 MiB (8,192,000 bytes)

Every character up to and inside the piece is one octet, so offset 7 and length 5 are the same in characters and in bytes.

8 KiB collected on 1,000 connections at once can hold up to 7.81 MiB.

What the model leaves out: grapheme clusters (a letter and a combining accent count as two characters here), Unicode normalisation, chunked and compressed bodies, and how the platform accounts for its own memory.

When a large download breaks: Bug 1474877

One record belongs on every checklist for HTML and Rewrite profiles. Bug 1474877: on a virtual server with an HTML or Rewrite profile, a download whose decompressed response exceeds tmm.deflate.memory.threshold, 4718592 bytes, fails at the client, which receives resets citing a compression error. As the Bug Tracker reads on 26 October 2026 (record last modified 18 September 2026), the listed known affected versions start at 16.1.4 and end at 21.0.0.3, with no 21.1.x release among them, and the fix is in 21.1.0, 17.5.1.4 and 17.1.3.5. The fix raises the threshold to about 9 MB and allows disabling it with the value 0, with F5's own warning that disabling it altogether can expose the system to zip bomb attacks.

The record's workaround is to decide from the request URI which responses should not be rewritten, such as large downloads, and to disable the Rewrite profile for those responses with REWRITE::disable. Read the command's page before you copy that pattern: REWRITE::disable switches the rewrite plugin to passthrough "for the lifetime of the TCP connection or until REWRITE::enable is called", so on a kept-alive connection the responses that follow stay unrewritten unless the rule turns rewriting back on, and the REWRITE::enable page warns that its use "can be extremely tricky to get exactly right" (REWRITE::disable, REWRITE::enable). On a fixed release, the upgrade is the cleaner answer.

Sources