You are currently browsing the tag archive for the ‘ltm’ tag.

As I discussed in a previous post, simply redirecting a user to a “friendly” 404 page isn’t the best option. First, the user might not remember what they clicked/typed to get them to the page and also, simply clicking the back button might not be an option, especially if they submitted form data. Fortunately, as F5 LTMs are “Strategic Points of Control,” we can use them to better handle the situation.


First off, let’s determine the desired behavior when a user request induced an error code. In our case, let’s choose 404 as the error code to watch for. If we detect this error code being sent to the user, let’s redirect them to our Home Page ( rather than simply keeping them at an error page. To make their experience better, let’s also hold the user at an custom page for a few seconds while explaining their issue as well as the request that caused the problem.


Since we’re looking for error codes sent by the servers, we’ll need to run our commands from within the “HTTP_RESPONSE” event. As you’ll see from the DevCentral wiki page for HTTP_RESPONSE, there are examples for using the “HTTP::status” command to detect error codes and redirect users. For some, the rule below is perfectly acceptable.



if { [HTTP::status] eq “404” } {

HTTP::redirect “” }



Unfortunately, that rule would result in the user being sent to the redirect page without any explanation as to what they did wrong. So, we’re going to beef the rule up a bit. As you’ll recall from this post, we can set variables from the HTTP_REQUEST event and then reference them from our HTTP_RESPONSE event in order to show the user the link that caused the error.

Here’s a nice sample rule I just whipped up. We’re using “HTTP::respond” so the response comes directly from LTM. Also, I’m setting a variable “delay” to the amount of seconds to keep the user at the hold page.



set hostvar [HTTP::host]

set urivar [HTTP::uri]

set delay 4



if { [HTTP::status] eq “404 } {

HTTP::respond 200 content \ “<html><head><title>Custom Error Page</title></head><body><meta http-equiv=’REFRESH” content=$delay;url=></head>\<p><h2>Unfortunately, your request for $hostvar$urivar casued a 404 error. After 4 seconds, you’ll automatically be redirected to our home page. If you feel you’ve tried a valid link, please contact Sorry for this inconvenience.</h2></p></body></html>” “Content-Type” “text/html”




So, with that rule, the user requests a page that causes a 404 error. LTM will detect the 404 error, and instead of sending it to the user, it will respond with a 200 status code and HTML showing the user the link the requested as well as apologizing and telling them to contact your webmaster if there’s an issue. I was too lazy to use the HTML to make the e-mail address clickable, maybe next time. Also, by using “Meta Refresh,” we’re holding the user at the page for 4 seconds and then sending them to our error page. As you can see, HTTP::respond is a very powerful command. It’s pretty cool being able to use LTM to send HTML to a user.




Conserving public IP addresses has always been a good idea. Naturally, it’s become more important lately but that’s neither here nor there as far as this post goes.

Let’s assume you’re managing a website powered by an F5 BIG-IP LTM. You’ve got the following setup:

1. Virtual Server with IP Address and listening on port 80.

2. A Pool called “pool_webservers” containing web servers,,,, and

3. A DNS record “” pointing to the Virtual Server’s IP Address of

While the site is working fine, you’d like to be able to access individual web servers from an external network. This way, if a customer tells you your site isn’t working, you can test each server individually to try and narrow it down. Also, perhaps you’re releasing code to individual servers and would like to make sure it looks good.

This is a very common requirement for sites. Unfortunately, since your servers are using non-internet routable addresses from the network, you can’t hit them externally.

People frequently deal with such an issue by doing one of the following:

1. Assign public IP addresses to each server and creating DNS records accordingly.

In this case, DNS might look like this:,, etc.

2. Create NATs on a public-facing router or Load Balancer to translate the public IPs to the server’s private ones.

In this case, DNS would look the same as above.

Unless you’re using port translation ( = server 1, = server 2 etc,) then you’re using a Public IP address for each server you’d like to access. Since larger sites typically have far more than 5 servers, it’s each to chew up Public Addresses quickly.

Fortunately, we can use iRules to “route” requests to the proper web servers without using a single additional Public IP Address. From the DevCentral iRules Commands page, you’ll notice an event called “HTTP::host.” When a user types “” into their browser, their HTTP request contains an “HTTP Header” that contains the host (,) they requested.

If you’ll remember our layout, we have a Virtual Server at served by “pool_webservers” with members,,,, and points to and is how users access the site. Now, we’d like the ability to target individual pool members from outside the network. Typically, this would require a public IP address for each web server but with iRules, we’re all set.

First, we’re going to create additional DNS records. Fortunately, they’re all going to point at the same address as the other ones. Our DNS zone for “” now looks like this:

www IN A

www1 IN A

www2 IN A

www3 IN A

www4 IN A

www5 IN A

Now, it’s time to put together our iRule. As I was extremely inspired by Joe Pruitt’s recent post comparing iRule Control Statements, I thought I’d give multiple examples of how to accomplish our goal.

First, we’ll go with a simple “else, if” rule.


if { [string tolower [HTTP::host]] eq “” } {

pool pool_webservers member 80

} elseif { [string tolower [HTTP::host]] eq “” } {

pool pool_webservers member 80

} elseif { [string tolower [HTTP::host]] eq “” } {

pool pool_webservers member 80

} elseif { [string tolower [HTTP::host]] eq “” } {

pool pool_webservers member 80

} elseif { [string tolower [HTTP::host]] eq “” } {

pool pool_webservers member 80



Well, that was painless enough. If a user’s host header is “,” we’re sending them to Simply bind that iRule to our Virtual Server and we’re set. You might also notice I’m using “string tolower.” That just converts the value to lowercase so I don’t have to support users inputting combinations of upper and lower case characters. Most browsers automatically convert the host header to lowercase but not all. If you read either “control statement” post above, you’ll notice that if/elses are hardly the most efficient method for doing something like this.

Now, we’ll try a “switch statement.”


switch -glob [string tolower [HTTP::host]] {

“” { pool pool_webservers member 80 }

“” { pool pool_webservers member 80 }

“” { pool pool_webservers member 80 }

“” { pool pool_webservers member 80 }

“” { pool pool_webservers member 80 }

default { pool pool_webservers }



This is a much cleaner, more efficient option. As you’ll notice, I used “-glob” with switch. Glob allows you to use wildcards and also look for patterns. If you read the above post comparing control statements, you’ll notice -glob isn’t as efficient as just using switch. Since we aren’t doing any pattern/wildcard matching here, you could easily leave off the -glob. I like to use it just in case I decide to add such enhancements later. I also used a “default” statement so requests not matching the other statements would go to our normal pool.

My personal preference is to use “classes/data groups.” A class is essentially a list that can be searched or matched. Typically, you have the field you’re matching and a value you can record should that value be matched. In version 10, the class features were greatly enhanced.

For our sample rule, our class could look like this:

class host_headers {


“” { “” }

“” { “” }

“” { “” }

“” { “” }

“” { “” }



In this case, “” is what we’re matching against and “” is the value we’d like to return. If simply using “class match,” we can ignore/omit the value on the right. If using “class search -value,” then we’re trying to return it. Here’s an example:


if { [class match [string tolower [HTTP::host]] eq host_headers] } {

set hostvar [class search – value host_headers eq [string tolower [HTTP::host]]]

pool pool_webservers member $hostvar 80 }


The first thing we did was compare the host header to our class/datagroup called “host_headers.” If there’s a match, we set a variable called “hostvar” to the corresponding value. If the user requested “,” for instance, the corresponding value in the class is “” So, now that “hostvar” =, we reference the variable in our pool command. So, the pool command essentially became “pool pool_webservers member 80.”

Joe’s “Comparing iRule Control Statements” showed that using classes was ridiculously efficient. Using classes can make it a bit more difficult to understand what an iRule does as it requires reading the rule and then reading the class contents. With that said, it’s very efficient and minimizes the amount of text within the rule. The ability to extract a value is very nice too.

To “complicate” things a bit, let’s assume you don’t want people outside of your IP space to access individual servers. If you’re releasing new code or price updates, there’s a fair chance you don’t want people hitting the system being worked on. To accomplish this, let’s create an address-type “data group/class.” containing the IP Address or Network we’d like to allow access. Let’s assume this class is called “allowed_access”


if { [class match [string tolower [HTTP::host]] eq host_headers] and ! [class match [IP::client_addr] eq allowed_access] } {

HTTP::respond 403 “You’re not allowed!” }

else {

set hostvar [class search – value host_headers eq [string tolower [HTTP::host]]]

pool pool_webservers member $hostvar 80 }


Now, if a user requests one of our “specific server host-headers,” but doesn’t match the allowed IP addresses class, we’re going to respond with an HTTP 403. If they do match both conditions, the rule should operate normally.

While my examples used iRules to target specific servers using host headers, it shouldn’t stop there. Let’s say you’re administering tons of different sites similar to the following: = main company page = a domain registrar site you’re hosting = you’ve jumped on the social networking bandwagon and are hosting facebook variant = self explanatory

It’s fair to assume you’d have different web servers hosting these sites. Typically, you’d have a different Virtual Server as well as the corresponding public IP as well. That’s not always necessary though. Using our switch statement from above, we can change our pool command a bit.


switch -glob [string tolower [HTTP::host]] {

“” { pool pool_sample}

“” { pool pool_domain }

“” { pool pool_social }

“” { pool pool_dating }

default { pool pool_default }



One of the more popular e-mail/forum signatures I see is “with iRules, you can.” I think this is a great example. Since LTM is a “Strategic Point of Control,” it can extract information such as the Host Header, or a Requested URI, and react to it.

It shouldn’t surprise anyone that I enjoy new technical challenges. While I think I’ve become pretty decent at writing iRules, I’m constantly reminded of how much more I have to learn.

Yesterday, someone posted a question on DevCentral that I couldn’t initially answer. They were running an online forum and wanted to keep a user from posting spam. Their idea was to search the post when it was submitted and if it contained a “blocked word,” prevent the post from being made. Unfortunately, the vast majority of my experience with iRules has been around inspecting HTTP GET requests and responses. In order to accomplish what this user wanted, the iRule would have to search the Payload of an HTTP POST which was new to me.


Fortunately, there were plenty of examples on DevCentral where people did something similar.  One of the most popular examples is for Sanitizing Credit Card Numbers. That iRule searches the response payload for strings that match credit card patterns. In this case, we’re searching the request data instead.


While the vast majority of rules I’ve seen only care about requests and responses, this was such an awesome reason to look at the payload, I thought I had to learn and also had to share it. Thanks to DevCentral user Hoolio’s posts as well as the awesome wiki, I had a relatively easy time learning. Yet another great reason for leveraging your “Strategic Points of Control” I’m curious to know what other uses for inspecting request/response data people could think of.


Here’s the code I ended up recommending.



   # Only check POST requests
   if { [HTTP::method] eq "POST" } {

      # Default amount of request payload to collect (in bytes)
      set collect_length 2048

      # Check for a non-existent Content-Length header
      if {[HTTP::header Content-Length] eq ""}{

         # Use default collect length of 2k for POSTs without a Content-Length header
         set collect_length $collect_length

      } elseif {[HTTP::header Content-Length] == 0}{

         # Don't try collect a payload if there isn't one
         unset collect_length

      } elseif {[HTTP::header Content-Length] > $collect_length}{

         # Use default collect length
         set collect_length $collect_length

      } else {

         # Collect the actual payload length
         set collect_length [HTTP::header Content-Length]


      # If the POST Content-Length isn't 0, collect (a portion of) the payload
      if {[info exists collect_length]}{

         # Trigger collection of the request payload
         HTTP::collect $collect_length

# Define a string-type datagroup called dg_blocked containing words to be blocked
   if { [matchclass [HTTP::payload] contains dg_blocked] }{
      HTTP::respond 403 "Blocked"


I’ve only recently started to look at the blog statistics provided by wordpress. One of my favorite data points is the “searches” through which users find my blog. One of the most popular searches pertains to having your F5 LTM use an iRule to send users to a maintenance page if all servers in a pool are down. Since I hate the idea of F5 customers being unable to leverage their device for a very common scenario like this, I thought I’d write a post.


As a reminder, there are plenty of examples of this exact scenario on


First, the easy way…simply utilize a “fallback host” in an HTTP Profile attached to your Virtual Server. If LTM is unable to connect to a pool member to serve a request, it’ll send the customer a redirect.


As you’ll notice, the post above also illustrates how to use an iRule for a similar task.

when LB_FAILED {

if { [active_members [LB::server pool]] < 1 } {

HTTP::fallback “; }}


I rewrote the example rule a bit, but the point is there. If the pool to which the user was attached has less than 1 active member, then utilize a fallback. It’s important to note that the pool members are only inactive if they’ve failed their health checks. So, if you’re using a tcp port check and a pool member is throwing 500s for every request, it’ll remain up. In order to combat this, you can either use a better health check, or build additional logic into your rule.



if { [HTTP::status] eq “500” } {

HTTP::redirect “; }}


Now, rather than looking at the number of active pool members, we’re redirecting users if their pool member sent a 500. The negative to this method is that the pool might have other members that aren’t serving 500s which is why reselecting a pool member might be the better options. I’ll touch on that in another post.


As I discussed in my post about “Strategic Points of Control,” F5 LTMs are in a great position to capture and report on information. I’ve recently encountered several issues where I needed to log the systems sending HTTP 404/500 responses and the URLs for which they were triggered. While this information can be obtained from a packet capture, I find it much easier to simply leverage iRules to log the information.


If you don’t know too much about iRules, I’d encourage you to head over to DevCentral and do some reading. One of the first things you’ll learn is that there are several “events” in which an iRule can inspect and react to traffic. Each event has different commands that can be used. While some commands can be used in multiple events, some may not.


As an example, HTTP::host and HTTP::uri can be used in the HTTP_REQUEST event, but not in the HTTP_RESPONSE event. Since an HTTP Error Response sent by a server would occur in the HTTP_RESPONSE event (between server and LTM,) we can’t simply log the value of HTTP::host or HTTP::uri as those commands aren’t usable in the HTTP_RESPONSE context. Fortunately, variables can be set in one event and referenced in another which allows us to still access the proper information.


Here’s an overview of what we’re trying to accomplish:


1. A client makes a request to a Virtual Server on the LTM.

2. The LTM sends this request to a pool member.

3. If the pool member (server) responds with an HTTP Status code of 500, we want to log the Pool Member’s IP, the requested HTTP Host and URI, and the Client’s IP address.


We’ll be using the “HTTP::status” command to check for 500s. Since this command needs to be executed within the HTTP_RESPONSE event which doesn’t have access to HTTP::host or HTTP::uri, we’ll need to use variables.

From the HTTP_REQUEST event, we’ll utilize said variables to track the value of HTTP::host, HTTP::uri, and IP::client_addr.

The HTTP_REQUEST event in our iRule will look something like this:


set hostvar [HTTP::host]

set urivar [HTTP::uri]

set ipvar [IP::client_addr] }

Now, we’ll check the HTTP status code from within the HTTP_RESPONSE event and if it’s a 500, we’ll log the value of the variables above.


if { [HTTP::status] eq 500 } {

log local0. “$ipvar requested $hostvar $urivar and received a 500 from [IP::server_addr]” }}


Now, whenever a 500 is sent, you can simply check your LTM logs and you’ll see the client who received it, the server that sent it, and the URL that caused it. This is a fairly vanilla implementation. I’ve had several situations in which I needed to also report on the value of a JSESSIONID cookie so our app folks could also check their logs. In a situation like that, you’d simply set and call another variable.


set appvar [HTTP::cookie JSESSIONID]


log local0. “session id was $appvar”


This was a good example of how easily iRules can be leveraged to report on issues. Unfortunately though, this isn’t always a scalable option which is why I thought I’d talk about a product I’ve really enjoyed using.

The folks behind Extrahop call it an “Application Delivery Assurance” product. Since both co-founders came from F5, they have a great handle on Application Delivery and the challenges involved. Since I’m typically only concerned with HTTP traffic nowadays, I use Extrahop to track response times, alert on error responses, and also to baseline our environment. As an F5 user, I’m very pleased to see the product’s help section making recommendations on BIG-IP settings to tune if certain issues are seen.

I’d definitely encourage you to go check out some product literature. Since it’s not always fun to arrange a demo and talk to sales folks, they offer free analysis via Simply upload a packet capture, it’ll be run through an Extrahop unit, and you can see the technology in action.



As I discussed in this post, sending an HTTP GET for a page on a server to which you load balance traffic is one of the better health checks available. If you use the right page, it can be an extremely light-weight, yet highly reliable check.

In order to properly utilize these health checks, you need to know enough about the application you’re supporting to understand how it behaves when it fails.

In my case, I send traffic to a pool of Apache Servers running mod_weblogic. From there, the traffic is sent to application instances.


Using an F5 BIG-IP LTM as an example, there are several configurable parameters when defining a health check.


1. Interval (How often the check is sent)

2. Timeout (How long does the resource have to respond)

3. Send String (The request you’re sending the resource)

4. Receive String (What response causes the health check to pass?)

5. Receive Disable String (What response causes the health check to fail)


There’s several more, but let’s concentrate on the typical ones.

The default interval is 5 seconds while the timeout is 16. I’ve always been ok with that.

For our send string, let’s do “GET /login.jsp HTTP/1.1\r\nHost: \r\nConnection: Close\r\n\r\n”

So, we’re sending an HTTP GET for /login.jsp using HTTP/1.1 and an empty host header. We’re also closing out the connection so it doesn’t have to sit idle on the server.

For our receive string, let’s do “HTTP/1\.(0|1) (2)”

So, we’re considering a response starting with 2 using HTTP 1.0 or 1.1 as a success. Typically, a server will respond with a 200 when all is well so this is pretty typical.


Unfortunately for me, our resource actually sends an HTTP 301 (Permanent Redirect) when a user tries loading the login page. This happens fairly often, especially if you’re sending a health check for “/” and the resource redirects you to a different directory. Since we consider this permanent redirect to be normal behavior, we’ll modify our receive string to “HTTP/1\.(0|1) (2|3)” Now, we’re including all 3** responses as well. Since a failed resource will usually timeout or send a 404/500 when it fails, this should work well.


As I mentioned before, my LTM sends traffic to Apache which then sends it to our App instances via mod_weblogic. So, what happens when the app instances are down? I’d expect a 404 or 500 from Apache, right? Sure, as long as your application folks haven’t configured it to send an HTTP 302 (Temporary Redirect) so users go to a custom error page when the App Instances are down.


So, here’s what we’ve seen:


1. During normal conditions, the resource returns a 301 for its health check.

2. If application instances are down, the resource returns a 302 for its health check.


Naturally, we need to modify our Receive String


HTTP/1\.(0|1) (2|3)


HTTP/1\.(0|1) (2|301)


We’re still allowing any 2xx response but are now only allowing 301s.


We’ve done what we wanted to. We’ve configured a health check that accurately determines the system’s health. As you’ve noticed though, it required trial and error, and a lot of testing. When determining a health check strategy, it’s critical that either you or an application owner understands their application’s behavior while it’s working, and even more importantly, when it’s not. Also, it’s not always wise to “set and forget” these checks. If, for instance, our application folks changed the “/login.jsp” redirect from a 301 to a 302, the check would fail, and we’d have to come up with a new strategy.