Replacing a 404 (not found) with 301/302 (object moved …) using Citrix NetScaler ADC


last update: May 6th 2021

I recently moved my blog to a new host, so several objects don’t exist anymore. Unfortunately, this will lead to plenty of 404 not found, instead of giving users access to the website as a whole. So I decided to replace all not found with redirects to a certain page, it might be any kind of sorry page or the default page. It’s up to you.

The problem

Changing all of the HTTP responses using Citrix ADC (NetScaler) is not that easy. So I have to re-write the HTTP.RES.STATUS to a redirect and set the header location.

Which kind of redirect?

There are several kinds of redirects:

307 and 308 are similar to 301 and 302, but do not allow the HTTP method to change. So if the original HTTP method had been a POST the request to the new address will be a POST as well. The same happens to a PUT.
This had been the original idea for 301/302, however, browsers had been implemented differently, the succeeding request always is a GET, and the IETF (Internet Engineering Task Force) and the W3C (World Wide Web Consortium) had to react. That’s why they introduced these response codes in addition. We could use 307/308 in our case, but as they are rarely used, and I didn’t need their special features, so I decided not to use them.

303 is only used after an HTTP post.

So it would either be a 301 or a 302.

301 or 302

Many admins think, 301 and 302 are the same, or, at least, that 302 is to be preferred as it is the default setting on Citrix ADC / NetScaler. I don’t agree. Like always: it depends. “It depends” is the standard answer to all questions an architect like me would give. “What does it depend on?”, is the annoying succeeding question of every customer.

Even though architects don’t like to answer this question, I’ll try to give an answer to you.

What’s the difference?

Let’s see it from the perspective of a search engine like Google, Bing, or Yahoo. They use so-called crawlers.

A crawler reads all of a webpage and puts the content into its index. In addition, it also stores all links found on this page in a table. Succeeding requests will follow these links. That’s how they learn about content on the internet, that’s how they learn to decide between more and less important pages (the more sites link to a page, the more interesting it seems to be).

Every time, a crawler gets a redirect as a response to a request, it has to decide what to do in this case. Does it make sense to follow this redirect, or should the redirect get skipped? And what to do with the original content? Keep it in the index, or remove it?

301, Moved Permanently is a permanent redirect. A crawler will “think”, the original content is gone forever. As it won’t come back, the search engine deletes all the old content (it would frustrate users by leading them to the same redirect). However, the content this page is redirecting to might be of interest to users and will be put into the index as a replacement of the original one.

302, Moved previously (RFC-1945) had been called moved temporarily. Different from a 301, a crawler would think of something transient. Probably, there is an issue or a scheduled maintenance window, and it will get redirected to a sorry-page. There is absolutely no point in following this redirect, and the original content will continue being relevant to users, as soon as it comes back. So Google won’t delete the old content and won’t put the new one into its index. And that’s absolutely not what I wanted. So 302 is wrong for sure in my case.

So the answer to the question “Depends on what?” is: Is it a permanent redirect, or is it a temporary one? In case of permanent always redirects use 301, if temporary use only 302. There is a huge difference, even though users and admins won’t see.

The solution

We need two policies: The first one changing the response status, the second one to set the location header (the address of the redirect).


Setting the response status

The response status (HTTP.RES.STATUS can be changed using the action of type REPLACE:

Citrix ADC / NetScaler: send a 301 - the action to set the response status to 301

add rewrite action rw_act_301_Status replace HTTP.RES.STATUS "\"301 Moved Permanently\""

Setting the location

The location is transferred in a header called location, so we simply have to set this header:

Citrix ADC / NetScaler: send a 301 - the action to set the location to redirect

add rewrite action rw_act_301_location_falsch replace "HTTP.RES.HEADER(\"Location\")" "\"\""


There are two approaches:

  • Create two policies and bind them to the HTTP response side.
  • Create a policy label and invoke it.

The policy expression

No matter, which solution you prefer, the policy expression will always look like this:
"HTTP.RES.STATUS.EQ(404) && HTTP.REQ.HEADER(\"Accept\").CONTAINS_ANY(\"media_type\")"

  • see if the response status is a 404
  • ssee if the requested content had been of a media type of interest (text/plain, text/html, text/php). I don’t do a redirect for images, style-sheets, scripts, …, as they are relevant to a browser only, not to a user.

So my first action would be: Create the pattern-set, containing all the relevant media types …

Citrix ADC / NetScaler: send a 301 - the pattern set
add policy patset media_type
bind policy patset media_type "text/plain" -index 1
bind policy patset media_type "text/php" -index 2
bind policy patset media_type "text/html" -index 3

… and the policy. I prefer using policy labels, so I get everything in one place. The policy invoking the label got the policy expression filtering the 404s with the media type desired, the policies within the label simply get a true.

Citrix ADC / NetScaler: send a 301 - the policy invoking the policy label

Binding it all together

I bind it, using a policy label, as described here:
Citrix ADC / NetScaler: send a 301 - the policy label
add rewrite policylabel rw_res_label_301 http_res
bind rewrite policylabel rw_res_label_301 rw_pol_301_location 100 NEXT
bind rewrite policylabel rw_res_label_301 rw_pol_301_Status 110 NEXT

invoking the policy label

Citrix ADC / NetScaler: send a 301 - invoking the policy label

bind lb vserver -policyName rw_pol_call301Label -priority 100 -gotoPriorityExpression NEXT -type RESPONSE -invoke policylabel rw_res_label_301

Like always: I’d be glad to get feedback on this. Tell me, if you have a better idea, tell me if you like it. And please tell me if you don’t agree, found mistakes, …!


About the author

Johannes Norz

Johannes Norz is a Citrix Certified Citrix Technology Advocate (CTA), Citrix Certified Instructor (CCI) and Citrix Certified Expert on Application Delivery and Security (CCE-AppDS).

He frequently works for Citrix international Consulting Services and several education centres all around the globe.

Johannes lives in Austria. He had been borne in Innsbruck, a small city (150.000 inhabitants) in the middle of the most beautiful Austrian mountains (

Add comment

By Johannes Norz

Recent Posts

Recent Comments