Repository: kubernetes-sigs/gateway-api

Issue:
Incorrect validation or description for `HTTPHeaderFilter` in `HTTPRoute`
<!--
Thank you for your interest in Gateway API! Please note that bug reports
here should only be used for bugs with the API itself, such as:

- Incomplete or inaccurate validation
- Problems with the API specification
- Flaws in conformance tests

Bugs that don't fit into one of the above categories should likely be filed
with the [implementation](https://gateway-api.sigs.k8s.io/implementations/)
of Gateway API that you're using.

Please use this template while reporting a bug and provide as much info as
possible. Not doing so may result in your bug not being addressed in a timely
manner. Thank you!
-->
**What happened**:
The current documentation for the `HTTPHeaderFilter` type in the `HTTPRoute` Custom Resource (CR) specifies the validation rules as follows:
> Only one action for a given header name is permitted. Filters specifying multiple actions of the same or different type for any one header name are invalid and will be rejected by CRD validation.

However, this description does not accurately reflect the actual validation behavior:
- Filters specifying multiple actions of the **same** or different type for any one header name are invalid:
  ```
  requestHeaderModifier:
    add:
      - name: my-header-name
        value: my-header-value
      - name: my-header-name
        value: my-header-value
  ```
  **Error**:  `The HTTPRoute "backend" is invalid: spec.rules[0].filters[0].requestHeaderModifier.add[1]: Duplicate value: map[string]interface {}{"name":"my-header-name"}`

- Filters specifying multiple actions of the same or **different** type for any one header name are invalid:
  ```
  requestHeaderModifier:
    add:
      - name: my-header-name
        value: my-header-value
    set:
      - name: my-header-name
        value: my-header-value
  ```
  **No Error**: `httproute.gateway.networking.k8s.io/backend configured`


**What you expected to happen**:
The documentation should accurately describe the validation rules. If multiple actions of different types are indeed permitted for the same header, this should be clearly stated in the documentation. Otherwise, if the current behavior is unintended, the validation logic should be updated to match the documented behavior.

**How to reproduce it (as minimally and precisely as possible)**:
Define an `HTTPRoute` with the `requestHeaderModifier` mentioned above.

**Anything else we need to know?**:
N/A

Hints from issue comments:
@snorwin: This issue could potential be combined with: #2277

@robscott: @snorwin thanks for the detailed writeup! This is actually more nuanced than it seems. In some cases we have stated in the spec that something is invalid despite not having corresponding CEL validation, this may be one of those cases. Essentially CEL came into existence after much of the spec was written, so in some cases we had to rely on controllers to do this kind of validation instead of CEL. While this does seem like something we could tighten with CEL validation, now that the API is already GA, a chance like that would be not be backwards compatible. 

As far as this specific case, it seems like it could at least theoretically be desirably to `remove` and then `add` a header if some implementations don't support `set`, but otherwise I'm struggling to find a use case for allowing different operations on the same header name.

/cc @youngnick @mlavacca @k8s-triage-robot: The Kubernetes project currently lacks enough contributors to adequately respond to all issues.

This bot triages un-triaged issues according to the following rules:
- After 90d of inactivity, `lifecycle/stale` is applied
- After 30d of inactivity since `lifecycle/stale` was applied, `lifecycle/rotten` is applied
- After 30d of inactivity since `lifecycle/rotten` was applied, the issue is closed

You can:
- Mark this issue as fresh with `/remove-lifecycle stale`
- Close this issue with `/close`
- Offer to help out with [Issue Triage][1]

Please send feedback to sig-contributor-experience at [kubernetes/community](https://github.com/kubernetes/community).

/lifecycle stale

[1]: https://www.kubernetes.dev/docs/guide/issue-triage/@snorwin: /remove-lifecycle stale@k8s-triage-robot: The Kubernetes project currently lacks enough contributors to adequately respond to all issues.

This bot triages un-triaged issues according to the following rules:
- After 90d of inactivity, `lifecycle/stale` is applied
- After 30d of inactivity since `lifecycle/stale` was applied, `lifecycle/rotten` is applied
- After 30d of inactivity since `lifecycle/rotten` was applied, the issue is closed

You can:
- Mark this issue as fresh with `/remove-lifecycle stale`
- Close this issue with `/close`
- Offer to help out with [Issue Triage][1]

Please send feedback to sig-contributor-experience at [kubernetes/community](https://github.com/kubernetes/community).

/lifecycle stale

[1]: https://www.kubernetes.dev/docs/guide/issue-triage/@snorwin: /remove-lifecycle stale

Solution patch:
diff --git a/README.md b/README.md
index 3dbd66b3..e8ebd1ad 100644
--- a/README.md
+++ b/README.md
@@ -71,7 +71,7 @@ Participation in the Kubernetes community is governed by the
 [spec]: https://gateway-api.sigs.k8s.io/reference/spec/
 [concepts]: https://gateway-api.sigs.k8s.io/concepts/api-overview
 [security-model]: https://gateway-api.sigs.k8s.io/concepts/security-model
-[gh_release]: https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.2.1
+[gh_release]: https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.3.0
 [godoc]: https://pkg.go.dev/sigs.k8s.io/gateway-api
 [conformance-docs]: https://gateway-api.sigs.k8s.io/concepts/conformance/
 [reports-readme]: ./conformance/reports/README.md
diff --git a/apis/v1/httproute_types.go b/apis/v1/httproute_types.go
index 6131d70b..6fec27d6 100644
--- a/apis/v1/httproute_types.go
+++ b/apis/v1/httproute_types.go
@@ -988,11 +988,11 @@ type HTTPHeader struct {
 }
 
 // HTTPHeaderFilter defines a filter that modifies the headers of an HTTP
-// request or response. Only one action for a given header name is permitted.
-// Filters specifying multiple actions of the same or different type for any one
-// header name are invalid and will be rejected by CRD validation.
-// Configuration to set or add multiple values for a header must use RFC 7230
-// header value formatting, separating each value with a comma.
+// request or response. Only one action for a given header name is
+// permitted. Filters specifying multiple actions of the same or different
+// type for any one header name are invalid. Configuration to set or add
+// multiple values for a header must use RFC 7230 header value formatting,
+// separating each value with a comma.
 type HTTPHeaderFilter struct {
 	// Set overwrites the request with the given header (name, value)
 	// before the action.
diff --git a/pkg/features/mesh.go b/pkg/features/mesh.go
index 711681c5..e2af0a60 100644
--- a/pkg/features/mesh.go
+++ b/pkg/features/mesh.go
@@ -50,6 +50,12 @@ const (
 	SupportMeshConsumerRoute FeatureName = "MeshConsumerRoute"
 	// This option indicates mesh support for HTTPRoute path rewrite (extended conformance)
 	SupportMeshHTTPRouteRewritePath FeatureName = "MeshHTTPRouteRewritePath"
+	// This option indicates mesh support for HTTPRoute scheme redirect (extended conformance)
+	SupportMeshHTTPRouteSchemeRedirect FeatureName = "MeshHTTPRouteSchemeRedirect"
+	// This option indicates mesh support for HTTPRoute port redirect (extended conformance)
+	SupportMeshHTTPRouteRedirectPort FeatureName = "MeshHTTPRouteRedirectPort"
+	// This option indicates mesh support for HTTPRoute path redirect (extended conformance)
+	SupportMeshHTTPRouteRedirectPath FeatureName = "MeshHTTPRouteRedirectPath"
 )
 
 var (
@@ -69,6 +75,24 @@ var (
 		Name:    SupportMeshHTTPRouteRewritePath,
 		Channel: FeatureChannelStandard,
 	}
+
+	// MeshHTTPRouteSchemeRedirect contains metadata for the MeshHTTPRouteSchemeRedirect feature.
+	MeshHTTPRouteSchemeRedirect = Feature{
+		Name:    SupportMeshHTTPRouteRewritePath,
+		Channel: FeatureChannelStandard,
+	}
+
+	// MeshHTTPRouteRedirectPort contains metadata for the MeshHTTPRouteRedirectPort feature.
+	MeshHTTPRouteRedirectPort = Feature{
+		Name:    SupportMeshHTTPRouteRedirectPort,
+		Channel: FeatureChannelStandard,
+	}
+
+	// MeshHTTPRouteRedirectPath contains metadata for the MeshHTTPRouteRedirectPath feature.
+	MeshHTTPRouteRedirectPath = Feature{
+		Name:    SupportMeshHTTPRouteRedirectPath,
+		Channel: FeatureChannelStandard,
+	}
 )
 
 // MeshExtendedFeatures includes all the supported features for the service mesh at
@@ -77,4 +101,6 @@ var MeshExtendedFeatures = sets.New(
 	MeshClusterIPMatchingFeature,
 	MeshConsumerRouteFeature,
 	MeshHTTPRouteRewritePath,
+	MeshHTTPRouteSchemeRedirect,
+	MeshHTTPRouteRedirectPath,
 )
diff --git a/site-src/api-types/httproute.md b/site-src/api-types/httproute.md
index a1e051ab..c9d96dee 100644
--- a/site-src/api-types/httproute.md
+++ b/site-src/api-types/httproute.md
@@ -322,7 +322,7 @@ metadata:
 ...
 status:
   parents:
-  - parentRefs:
+  - parentRef:
       name: gw-example
       namespace: gw-example-ns
     conditions:
diff --git a/site-src/guides/http-header-modifier.md b/site-src/guides/http-header-modifier.md
index a55e2b86..1118124e 100644
--- a/site-src/guides/http-header-modifier.md
+++ b/site-src/guides/http-header-modifier.md
@@ -1,7 +1,5 @@
 # HTTP Header Modifiers
 
-[HTTPRoute resources](../api-types/httproute.md) can modify the headers of HTTP requests and the HTTP responses from clients.
-There are two types of [filters](../api-types/httproute.md#filters-optional) available to meet these requirements: `RequestHeaderModifier` and `ResponseHeaderModifier`.
 [HTTPRoute resources](../api-types/httproute.md) can modify the headers of HTTP requests and the HTTP responses from clients.
 There are two types of [filters](../api-types/httproute.md#filters-optional) available to meet these requirements: `RequestHeaderModifier` and `ResponseHeaderModifier`.
 



Test patch:
diff --git a/conformance/tests/mesh/httproute-redirect-host-and-status.go b/conformance/tests/mesh/httproute-redirect-host-and-status.go
new file mode 100644
index 00000000..055b70a2
--- /dev/null
+++ b/conformance/tests/mesh/httproute-redirect-host-and-status.go
@@ -0,0 +1,84 @@
+/*
+Copyright 2025 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package meshtests
+
+import (
+	"testing"
+
+	"sigs.k8s.io/gateway-api/conformance/utils/echo"
+	"sigs.k8s.io/gateway-api/conformance/utils/http"
+	"sigs.k8s.io/gateway-api/conformance/utils/roundtripper"
+	"sigs.k8s.io/gateway-api/conformance/utils/suite"
+	"sigs.k8s.io/gateway-api/pkg/features"
+)
+
+func init() {
+	MeshConformanceTests = append(MeshConformanceTests, MeshHTTPRouteRedirectHostAndStatus)
+}
+
+var MeshHTTPRouteRedirectHostAndStatus = suite.ConformanceTest{
+	ShortName:   "MeshHTTPRouteRedirectHostAndStatus",
+	Description: "An HTTPRoute with hostname and statusCode redirect filters",
+	Features: []features.FeatureName{
+		features.SupportMesh,
+		features.SupportHTTPRoute,
+	},
+	Manifests: []string{"tests/mesh/httproute-redirect-host-and-status.yaml"},
+	Test: func(t *testing.T, s *suite.ConformanceTestSuite) {
+		ns := "gateway-conformance-mesh"
+		client := echo.ConnectToApp(t, s, echo.MeshAppEchoV1)
+
+		testCases := []http.ExpectedResponse{
+			{
+				Request: http.Request{
+					Host:             "echo",
+					Path:             "/hostname-redirect",
+					UnfollowRedirect: true,
+				},
+				Response: http.Response{
+					StatusCode: 302,
+				},
+				RedirectRequest: &roundtripper.RedirectRequest{
+					Host: "example.org",
+				},
+				Namespace: ns,
+			}, {
+				Request: http.Request{
+					Host:             "echo",
+					Path:             "/host-and-status",
+					UnfollowRedirect: true,
+				},
+				Response: http.Response{
+					StatusCode: 301,
+				},
+				RedirectRequest: &roundtripper.RedirectRequest{
+					Host: "example.org",
+				},
+				Namespace: ns,
+			},
+		}
+		for i := range testCases {
+			// Declare tc here to avoid loop variable
+			// reuse issues across parallel tests.
+			tc := testCases[i]
+			t.Run(tc.GetTestCaseName(i), func(t *testing.T) {
+				t.Parallel()
+				client.MakeRequestAndExpectEventuallyConsistentResponse(t, tc, s.TimeoutConfig)
+			})
+		}
+	},
+}
diff --git a/conformance/tests/mesh/httproute-redirect-host-and-status.yaml b/conformance/tests/mesh/httproute-redirect-host-and-status.yaml
new file mode 100644
index 00000000..8bb27798
--- /dev/null
+++ b/conformance/tests/mesh/httproute-redirect-host-and-status.yaml
@@ -0,0 +1,30 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: mesh-redirect-host-and-status
+  namespace: gateway-conformance-mesh
+spec:
+  parentRefs:
+  - group: ""
+    kind: Service
+    name: echo
+    port: 80
+  rules:
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /hostname-redirect
+    filters:
+    - type: RequestRedirect
+      requestRedirect:
+        hostname: example.org
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /host-and-status
+    filters:
+    - type: RequestRedirect
+      requestRedirect:
+        statusCode: 301
+        hostname: example.org
+
diff --git a/conformance/tests/mesh/httproute-redirect-path.go b/conformance/tests/mesh/httproute-redirect-path.go
new file mode 100644
index 00000000..bc4731e7
--- /dev/null
+++ b/conformance/tests/mesh/httproute-redirect-path.go
@@ -0,0 +1,139 @@
+/*
+Copyright 2025 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package meshtests
+
+import (
+	"testing"
+
+	"sigs.k8s.io/gateway-api/conformance/utils/echo"
+	"sigs.k8s.io/gateway-api/conformance/utils/http"
+	"sigs.k8s.io/gateway-api/conformance/utils/roundtripper"
+	"sigs.k8s.io/gateway-api/conformance/utils/suite"
+	"sigs.k8s.io/gateway-api/pkg/features"
+)
+
+func init() {
+	MeshConformanceTests = append(MeshConformanceTests, MeshHTTPRouteRedirectPath)
+}
+
+var MeshHTTPRouteRedirectPath = suite.ConformanceTest{
+	ShortName:   "MeshHTTPRouteRedirectPath",
+	Description: "An HTTPRoute with scheme redirect filter",
+	Manifests:   []string{"tests/mesh/httproute-redirect-path.yaml"},
+	Features: []features.FeatureName{
+		features.SupportMesh,
+		features.SupportHTTPRoute,
+		features.SupportMeshHTTPRouteRedirectPath,
+	},
+	Test: func(t *testing.T, s *suite.ConformanceTestSuite) {
+		ns := "gateway-conformance-mesh"
+		client := echo.ConnectToApp(t, s, echo.MeshAppEchoV1)
+
+		testCases := []http.ExpectedResponse{
+			{
+				Request: http.Request{
+					Host:             "echo",
+					Path:             "/original-prefix/lemon",
+					UnfollowRedirect: true,
+				},
+				Response: http.Response{
+					StatusCode: 302,
+				},
+				RedirectRequest: &roundtripper.RedirectRequest{
+					Path: "/replacement-prefix/lemon",
+				},
+				Namespace: ns,
+			}, {
+				Request: http.Request{
+					Host:             "echo",
+					Path:             "/full/path/original",
+					UnfollowRedirect: true,
+				},
+				Response: http.Response{
+					StatusCode: 302,
+				},
+				RedirectRequest: &roundtripper.RedirectRequest{
+					Path: "/full-path-replacement",
+				},
+				Namespace: ns,
+			}, {
+				Request: http.Request{
+					Host:             "echo",
+					Path:             "/path-and-host",
+					UnfollowRedirect: true,
+				},
+				Response: http.Response{
+					StatusCode: 302,
+				},
+				RedirectRequest: &roundtripper.RedirectRequest{
+					Host: "example.org",
+					Path: "/replacement-prefix",
+				},
+				Namespace: ns,
+			}, {
+				Request: http.Request{
+					Host:             "echo",
+					Path:             "/path-and-status",
+					UnfollowRedirect: true,
+				},
+				Response: http.Response{
+					StatusCode: 301,
+				},
+				RedirectRequest: &roundtripper.RedirectRequest{
+					Path: "/replacement-prefix",
+				},
+				Namespace: ns,
+			}, {
+				Request: http.Request{
+					Host:             "echo",
+					Path:             "/full-path-and-host",
+					UnfollowRedirect: true,
+				},
+				Response: http.Response{
+					StatusCode: 302,
+				},
+				RedirectRequest: &roundtripper.RedirectRequest{
+					Host: "example.org",
+					Path: "/replacement-full",
+				},
+				Namespace: ns,
+			}, {
+				Request: http.Request{
+					Host:             "echo",
+					Path:             "/full-path-and-status",
+					UnfollowRedirect: true,
+				},
+				Response: http.Response{
+					StatusCode: 301,
+				},
+				RedirectRequest: &roundtripper.RedirectRequest{
+					Path: "/replacement-full",
+				},
+				Namespace: ns,
+			},
+		}
+		for i := range testCases {
+			// Declare tc here to avoid loop variable
+			// reuse issues across parallel tests.
+			tc := testCases[i]
+			t.Run(tc.GetTestCaseName(i), func(t *testing.T) {
+				t.Parallel()
+				client.MakeRequestAndExpectEventuallyConsistentResponse(t, tc, s.TimeoutConfig)
+			})
+		}
+	},
+}
diff --git a/conformance/tests/mesh/httproute-redirect-path.yaml b/conformance/tests/mesh/httproute-redirect-path.yaml
new file mode 100644
index 00000000..05975f32
--- /dev/null
+++ b/conformance/tests/mesh/httproute-redirect-path.yaml
@@ -0,0 +1,76 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: mesh-redirect-path
+  namespace: gateway-conformance-mesh
+spec:
+  parentRefs:
+  - group: ""
+    kind: Service
+    name: echo
+    port: 80
+  rules:
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /original-prefix
+    filters:
+    - type: RequestRedirect
+      requestRedirect:
+        path:
+          type: ReplacePrefixMatch
+          replacePrefixMatch: /replacement-prefix
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /full
+    filters:
+    - type: RequestRedirect
+      requestRedirect:
+        path:
+          type: ReplaceFullPath
+          replaceFullPath: /full-path-replacement
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /path-and-host
+    filters:
+    - type: RequestRedirect
+      requestRedirect:
+        hostname: example.org
+        path:
+          type: ReplacePrefixMatch
+          replacePrefixMatch: /replacement-prefix
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /path-and-status
+    filters:
+    - type: RequestRedirect
+      requestRedirect:
+        path:
+          type: ReplacePrefixMatch
+          replacePrefixMatch: /replacement-prefix
+        statusCode: 301
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /full-path-and-host
+    filters:
+    - type: RequestRedirect
+      requestRedirect:
+        hostname: example.org
+        path:
+          type: ReplaceFullPath
+          replaceFullPath: /replacement-full
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /full-path-and-status
+    filters:
+    - type: RequestRedirect
+      requestRedirect:
+        path:
+          type: ReplaceFullPath
+          replaceFullPath: /replacement-full
+        statusCode: 301
diff --git a/conformance/tests/mesh/httproute-redirect-port.go b/conformance/tests/mesh/httproute-redirect-port.go
new file mode 100644
index 00000000..cae14c23
--- /dev/null
+++ b/conformance/tests/mesh/httproute-redirect-port.go
@@ -0,0 +1,113 @@
+/*
+Copyright 2025 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package meshtests
+
+import (
+	"testing"
+
+	"sigs.k8s.io/gateway-api/conformance/utils/echo"
+	"sigs.k8s.io/gateway-api/conformance/utils/http"
+	"sigs.k8s.io/gateway-api/conformance/utils/roundtripper"
+	"sigs.k8s.io/gateway-api/conformance/utils/suite"
+	"sigs.k8s.io/gateway-api/pkg/features"
+)
+
+func init() {
+	MeshConformanceTests = append(MeshConformanceTests, MeshHTTPRouteRedirectPort)
+}
+
+var MeshHTTPRouteRedirectPort = suite.ConformanceTest{
+	ShortName:   "MeshHTTPRouteRedirectPort",
+	Description: "An HTTPRoute with a port redirect filter",
+	Manifests:   []string{"tests/mesh/httproute-redirect-port.yaml"},
+	Features: []features.FeatureName{
+		features.SupportMesh,
+		features.SupportHTTPRoute,
+		features.SupportMeshHTTPRouteRedirectPort,
+	},
+	Test: func(t *testing.T, s *suite.ConformanceTestSuite) {
+		ns := "gateway-conformance-mesh"
+		client := echo.ConnectToApp(t, s, echo.MeshAppEchoV1)
+
+		testCases := []http.ExpectedResponse{
+			{
+				Request: http.Request{
+					Host:             "echo",
+					Path:             "/port",
+					UnfollowRedirect: true,
+				},
+				Response: http.Response{
+					StatusCode: 302,
+				},
+				RedirectRequest: &roundtripper.RedirectRequest{
+					Port: "8083",
+				},
+				Namespace: ns,
+			}, {
+				Request: http.Request{
+					Host:             "echo",
+					Path:             "/port-and-host",
+					UnfollowRedirect: true,
+				},
+				Response: http.Response{
+					StatusCode: 302,
+				},
+				RedirectRequest: &roundtripper.RedirectRequest{
+					Host: "example.org",
+					Port: "8083",
+				},
+				Namespace: ns,
+			}, {
+				Request: http.Request{
+					Host:             "echo",
+					Path:             "/port-and-status",
+					UnfollowRedirect: true,
+				},
+				Response: http.Response{
+					StatusCode: 301,
+				},
+				RedirectRequest: &roundtripper.RedirectRequest{
+					Port: "8083",
+				},
+				Namespace: ns,
+			}, {
+				Request: http.Request{
+					Host:             "echo",
+					Path:             "/port-and-host-and-status",
+					UnfollowRedirect: true,
+				},
+				Response: http.Response{
+					StatusCode: 302,
+				},
+				RedirectRequest: &roundtripper.RedirectRequest{
+					Port: "8083",
+					Host: "example.org",
+				},
+				Namespace: ns,
+			},
+		}
+		for i := range testCases {
+			// Declare tc here to avoid loop variable
+			// reuse issues across parallel tests.
+			tc := testCases[i]
+			t.Run(tc.GetTestCaseName(i), func(t *testing.T) {
+				t.Parallel()
+				client.MakeRequestAndExpectEventuallyConsistentResponse(t, tc, s.TimeoutConfig)
+			})
+		}
+	},
+}
diff --git a/conformance/tests/mesh/httproute-redirect-port.yaml b/conformance/tests/mesh/httproute-redirect-port.yaml
new file mode 100644
index 00000000..522d6732
--- /dev/null
+++ b/conformance/tests/mesh/httproute-redirect-port.yaml
@@ -0,0 +1,48 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: mesh-redirect-port
+  namespace: gateway-conformance-mesh
+spec:
+  parentRefs:
+  - group: ""
+    kind: Service
+    name: echo
+    port: 80
+  rules:
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /port
+    filters:
+    - type: RequestRedirect
+      requestRedirect:
+        port: 8083
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /port-and-host
+    filters:
+    - type: RequestRedirect
+      requestRedirect:
+        hostname: example.org
+        port: 8083
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /port-and-status
+    filters:
+    - type: RequestRedirect
+      requestRedirect:
+        port: 8083
+        statusCode: 301
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /port-and-host-and-status
+    filters:
+    - type: RequestRedirect
+      requestRedirect:
+        port: 8083
+        hostname: example.org
+        statusCode: 302
diff --git a/conformance/tests/mesh/httproute-redirect-scheme.go b/conformance/tests/mesh/httproute-redirect-scheme.go
new file mode 100644
index 00000000..e2417309
--- /dev/null
+++ b/conformance/tests/mesh/httproute-redirect-scheme.go
@@ -0,0 +1,116 @@
+/*
+Copyright 2025 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package meshtests
+
+import (
+	"testing"
+
+	"sigs.k8s.io/gateway-api/conformance/utils/echo"
+	"sigs.k8s.io/gateway-api/conformance/utils/http"
+	"sigs.k8s.io/gateway-api/conformance/utils/roundtripper"
+	"sigs.k8s.io/gateway-api/conformance/utils/suite"
+	"sigs.k8s.io/gateway-api/pkg/features"
+)
+
+func init() {
+	MeshConformanceTests = append(MeshConformanceTests, MeshHTTPRouteSchemeRedirect)
+}
+
+var MeshHTTPRouteSchemeRedirect = suite.ConformanceTest{
+	ShortName:   "MeshHTTPRouteSchemeRedirect",
+	Description: "An HTTPRoute with a scheme redirect filter",
+	Manifests:   []string{"tests/mesh/httproute-redirect-scheme.yaml"},
+	Features: []features.FeatureName{
+		features.SupportMesh,
+		features.SupportHTTPRoute,
+		features.SupportMeshHTTPRouteSchemeRedirect,
+	},
+	Test: func(t *testing.T, s *suite.ConformanceTestSuite) {
+		ns := "gateway-conformance-mesh"
+		client := echo.ConnectToApp(t, s, echo.MeshAppEchoV1)
+
+		testCases := []http.ExpectedResponse{
+			{
+				Request: http.Request{
+					Host:             "echo",
+					Path:             "/scheme",
+					UnfollowRedirect: true,
+				},
+				Response: http.Response{
+					StatusCode: 302,
+				},
+				RedirectRequest: &roundtripper.RedirectRequest{
+					Scheme: "https",
+				},
+				Namespace: ns,
+			},
+			{
+				Request: http.Request{
+					Host:             "echo",
+					Path:             "/scheme-and-host",
+					UnfollowRedirect: true,
+				},
+				Response: http.Response{
+					StatusCode: 302,
+				},
+				RedirectRequest: &roundtripper.RedirectRequest{
+					Host:   "example.org",
+					Scheme: "https",
+				},
+				Namespace: ns,
+			},
+			{
+				Request: http.Request{
+					Host:             "echo",
+					Path:             "/scheme-and-status",
+					UnfollowRedirect: true,
+				},
+				Response: http.Response{
+					StatusCode: 301,
+				},
+				RedirectRequest: &roundtripper.RedirectRequest{
+					Scheme: "https",
+				},
+				Namespace: ns,
+			},
+			{
+				Request: http.Request{
+					Host:             "echo",
+					Path:             "/scheme-and-host-and-status",
+					UnfollowRedirect: true,
+				},
+				Response: http.Response{
+					StatusCode: 302,
+				},
+				RedirectRequest: &roundtripper.RedirectRequest{
+					Scheme: "https",
+					Host:   "example.org",
+				},
+				Namespace: ns,
+			},
+		}
+		for i := range testCases {
+			// Declare tc here to avoid loop variable
+			// reuse issues across parallel tests.
+			tc := testCases[i]
+			t.Run(tc.GetTestCaseName(i), func(t *testing.T) {
+				t.Parallel()
+				client.MakeRequestAndExpectEventuallyConsistentResponse(t, tc, s.TimeoutConfig)
+			})
+		}
+	},
+}
diff --git a/conformance/tests/mesh/httproute-redirect-scheme.yaml b/conformance/tests/mesh/httproute-redirect-scheme.yaml
new file mode 100644
index 00000000..b5f4628c
--- /dev/null
+++ b/conformance/tests/mesh/httproute-redirect-scheme.yaml
@@ -0,0 +1,49 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: mesh-redirect-scheme
+  namespace: gateway-conformance-mesh
+spec:
+  parentRefs:
+  - group: ""
+    kind: Service
+    name: echo
+    port: 80
+  rules:
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /scheme
+    filters:
+    - type: RequestRedirect
+      requestRedirect:
+        scheme: "https"
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /scheme-and-host
+    filters:
+    - type: RequestRedirect
+      requestRedirect:
+        hostname: example.org
+        scheme: "https"
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /scheme-and-status
+    filters:
+    - type: RequestRedirect
+      requestRedirect:
+        scheme: "https"
+        statusCode: 301
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /scheme-and-host-and-status
+    filters:
+    - type: RequestRedirect
+      requestRedirect:
+        scheme: "https"
+        statusCode: 302
+        hostname: example.org
+
diff --git a/conformance/tests/mesh/httproute-rewrite-path.go b/conformance/tests/mesh/httproute-rewrite-path.go
index 79416a96..22f8ae66 100644
--- a/conformance/tests/mesh/httproute-rewrite-path.go
+++ b/conformance/tests/mesh/httproute-rewrite-path.go
@@ -34,8 +34,8 @@ var MeshHTTPRouteRewritePath = suite.ConformanceTest{
 	Description: "An HTTPRoute with path rewrite filter",
 	Features: []features.FeatureName{
 		features.SupportMesh,
-		features.SupportMeshHTTPRouteRewritePath,
 		features.SupportHTTPRoute,
+		features.SupportMeshHTTPRouteRewritePath,
 	},
 	Manifests: []string{"tests/mesh/httproute-rewrite-path.yaml"},
 	Test: func(t *testing.T, s *suite.ConformanceTestSuite) {