Repository: envoyproxy/gateway

Issue:
Ratelimit based on path and method
*Description*:
This feature request is follow up from Slack discussion with @arkodg .

The functionality for defining multiple rate limits was previously available through `RateLimitFilters`. However, these filters were removed from the codebase [around version 0.6.0](https://github.com/envoyproxy/gateway/issues/2006). A new CRD named `BackendTrafficPolicy` was introduced, and the `rateLimit` field now belongs to this CRD.

It would be beneficial to be able to define multiple rate limits again for entire paths within `BackendTrafficPolicy`. This would allow for a use case where a "basic" rate limit could be applied to unauthenticated clients, while a stricter "proper" rate limit could be applied to users with valid JWT authentication ([basic authentication is a separate consideration](https://github.com/envoyproxy/gateway/issues/2947)).


Discussion:
@arkodg: this should be possible with https://gateway.envoyproxy.io/docs/tasks/traffic/global-rate-limit/

Option 1 - match on `:path`
sometthing like
```
  rateLimit:
    type: Global
    global:
      rules:
      - clientSelectors:
        - headers:
          - name: :path
            value: /app
        limit:
          requests: 3
          unit: Hour
```          
Option 2
Create 2 HTTPRoutes (same hostname but different rules for different path matches) and apply a unique BTP for each HTTPRoute@arkodg: keeping this issue open so we can consider adding `path` and `method` (relates to https://github.com/envoyproxy/gateway/discussions/4448) as first class fields in the API so users dont need to rely on the pseudo headers 

Solution patch:
diff --git a/api/v1alpha1/envoygateway_types.go b/api/v1alpha1/envoygateway_types.go
index 27c1ded9d..42dd3adfc 100644
--- a/api/v1alpha1/envoygateway_types.go
+++ b/api/v1alpha1/envoygateway_types.go
@@ -272,7 +272,7 @@ type EnvoyGatewayKubernetesProvider struct {
 	// RateLimitPDB allows to control the pod disruption budget of rate limit service.
 	//
 	// +optional
-	RateLimitPDB *KubernetesPodDisruptionBudgetSpec `json:"rateLimitPdb,omitempty"`
+	RateLimitPDB *KubernetesPodDisruptionBudgetSpec `json:"rateLimitPDB,omitempty"`
 
 	// Watch holds configuration of which input resources should be watched and reconciled.
 	// +optional
diff --git a/api/v1alpha1/ratelimit_types.go b/api/v1alpha1/ratelimit_types.go
index dee9155c1..bc895a244 100644
--- a/api/v1alpha1/ratelimit_types.go
+++ b/api/v1alpha1/ratelimit_types.go
@@ -5,6 +5,10 @@
 
 package v1alpha1
 
+import (
+	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
+)
+
 // RateLimitSpec defines the desired state of RateLimitSpec.
 // +union
 type RateLimitSpec struct {
@@ -183,17 +187,30 @@ type RateLimitCostMetadata struct {
 // RateLimitSelectCondition specifies the attributes within the traffic flow that can
 // be used to select a subset of clients to be ratelimited.
 // All the individual conditions must hold True for the overall condition to hold True.
+// And, at least one of headers or methods or path or sourceCIDR condition must be specified.
+//
+// +kubebuilder:validation:XValidation:rule="has(self.headers) || has(self.methods) || has(self.path) || has(self.sourceCIDR)",message="at least one of headers, methods, path or sourceCIDR must be specified"
 type RateLimitSelectCondition struct {
 	// Headers is a list of request headers to match. Multiple header values are ANDed together,
 	// meaning, a request MUST match all the specified headers.
-	// At least one of headers or sourceCIDR condition must be specified.
 	//
 	// +optional
 	// +kubebuilder:validation:MaxItems=16
 	Headers []HeaderMatch `json:"headers,omitempty"`
 
+	// Methods is a list of request methods to match. Multiple method values are ORed together,
+	// meaning, a request can match any one of the specified methods. If not specified, it matches all methods.
+	//
+	// +optional
+	Methods []MethodMatch `json:"methods,omitempty"`
+
+	// Path is the request path to match.
+	// Support Exact, PathPrefix and RegularExpression match types.
+	//
+	// +optional
+	Path *PathMatch `json:"path,omitempty"`
+
 	// SourceCIDR is the client IP Address range to match on.
-	// At least one of headers or sourceCIDR condition must be specified.
 	//
 	// +optional
 	SourceCIDR *SourceMatch `json:"sourceCIDR,omitempty"`
@@ -278,6 +295,39 @@ const (
 	HeaderMatchDistinct HeaderMatchType = "Distinct"
 )
 
+// MethodMatch defines the matching criteria for the HTTP method of a request.
+type MethodMatch struct {
+	// Value specifies the HTTP method.
+	Value gwapiv1.HTTPMethod `json:"value"`
+
+	// Invert specifies whether the value match result will be inverted.
+	//
+	// +optional
+	// +kubebuilder:default=false
+	Invert *bool `json:"invert,omitempty"`
+}
+
+// PathMatch defines the matching criteria for the HTTP path of a request.
+type PathMatch struct {
+	// Type specifies how to match against the value of the path.
+	//
+	// +optional
+	// +kubebuilder:default=PathPrefix
+	Type *gwapiv1.PathMatchType `json:"type,omitempty"`
+
+	// Value specifies the HTTP path.
+	//
+	// +kubebuilder:default="/"
+	// +kubebuilder:validation:MaxLength=1024
+	Value string `json:"value"`
+
+	// Invert specifies whether the value match result will be inverted.
+	//
+	// +optional
+	// +kubebuilder:default=false
+	Invert *bool `json:"invert,omitempty"`
+}
+
 // RateLimitValue defines the limits for rate limiting.
 type RateLimitValue struct {
 	Requests uint          `json:"requests"`
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 59e1c985c..c06d4d02c 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -4863,6 +4863,26 @@ func (in *Lua) DeepCopy() *Lua {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *MethodMatch) DeepCopyInto(out *MethodMatch) {
+	*out = *in
+	if in.Invert != nil {
+		in, out := &in.Invert, &out.Invert
+		*out = new(bool)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MethodMatch.
+func (in *MethodMatch) DeepCopy() *MethodMatch {
+	if in == nil {
+		return nil
+	}
+	out := new(MethodMatch)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *OIDC) DeepCopyInto(out *OIDC) {
 	*out = *in
@@ -5198,6 +5218,31 @@ func (in *PassiveHealthCheck) DeepCopy() *PassiveHealthCheck {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PathMatch) DeepCopyInto(out *PathMatch) {
+	*out = *in
+	if in.Type != nil {
+		in, out := &in.Type, &out.Type
+		*out = new(v1.PathMatchType)
+		**out = **in
+	}
+	if in.Invert != nil {
+		in, out := &in.Invert, &out.Invert
+		*out = new(bool)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PathMatch.
+func (in *PathMatch) DeepCopy() *PathMatch {
+	if in == nil {
+		return nil
+	}
+	out := new(PathMatch)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *PathSettings) DeepCopyInto(out *PathSettings) {
 	*out = *in
@@ -6031,6 +6076,18 @@ func (in *RateLimitSelectCondition) DeepCopyInto(out *RateLimitSelectCondition)
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
+	if in.Methods != nil {
+		in, out := &in.Methods, &out.Methods
+		*out = make([]MethodMatch, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	if in.Path != nil {
+		in, out := &in.Path, &out.Path
+		*out = new(PathMatch)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.SourceCIDR != nil {
 		in, out := &in.SourceCIDR, &out.SourceCIDR
 		*out = new(SourceMatch)
diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
index ecc9059d8..9dd115831 100644
--- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
+++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
@@ -948,12 +948,12 @@ spec:
                                   RateLimitSelectCondition specifies the attributes within the traffic flow that can
                                   be used to select a subset of clients to be ratelimited.
                                   All the individual conditions must hold True for the overall condition to hold True.
+                                  And, at least one of headers or methods or path or sourceCIDR condition must be specified.
                                 properties:
                                   headers:
                                     description: |-
                                       Headers is a list of request headers to match. Multiple header values are ANDed together,
                                       meaning, a request MUST match all the specified headers.
-                                      At least one of headers or sourceCIDR condition must be specified.
                                     items:
                                       description: HeaderMatch defines the match attributes
                                         within the HTTP Headers of the request.
@@ -994,10 +994,66 @@ spec:
                                       type: object
                                     maxItems: 16
                                     type: array
-                                  sourceCIDR:
+                                  methods:
+                                    description: |-
+                                      Methods is a list of request methods to match. Multiple method values are ORed together,
+                                      meaning, a request can match any one of the specified methods. If not specified, it matches all methods.
+                                    items:
+                                      description: MethodMatch defines the matching
+                                        criteria for the HTTP method of a request.
+                                      properties:
+                                        invert:
+                                          default: false
+                                          description: Invert specifies whether the
+                                            value match result will be inverted.
+                                          type: boolean
+                                        value:
+                                          description: Value specifies the HTTP method.
+                                          enum:
+                                          - GET
+                                          - HEAD
+                                          - POST
+                                          - PUT
+                                          - DELETE
+                                          - CONNECT
+                                          - OPTIONS
+                                          - TRACE
+                                          - PATCH
+                                          type: string
+                                      required:
+                                      - value
+                                      type: object
+                                    type: array
+                                  path:
                                     description: |-
-                                      SourceCIDR is the client IP Address range to match on.
-                                      At least one of headers or sourceCIDR condition must be specified.
+                                      Path is the request path to match.
+                                      Support Exact, PathPrefix and RegularExpression match types.
+                                    properties:
+                                      invert:
+                                        default: false
+                                        description: Invert specifies whether the
+                                          value match result will be inverted.
+                                        type: boolean
+                                      type:
+                                        default: PathPrefix
+                                        description: Type specifies how to match against
+                                          the value of the path.
+                                        enum:
+                                        - Exact
+                                        - PathPrefix
+                                        - RegularExpression
+                                        type: string
+                                      value:
+                                        default: /
+                                        description: Value specifies the HTTP path.
+                                        maxLength: 1024
+                                        type: string
+                                    required:
+                                    - value
+                                    type: object
+                                  sourceCIDR:
+                                    description: SourceCIDR is the client IP Address
+                                      range to match on.
                                     properties:
                                       type:
                                         default: Exact
@@ -1017,6 +1073,11 @@ spec:
                                     - value
                                     type: object
                                 type: object
+                                x-kubernetes-validations:
+                                - message: at least one of headers, methods, path
+                                    or sourceCIDR must be specified
+                                  rule: has(self.headers) || has(self.methods) ||
+                                    has(self.path) || has(self.sourceCIDR)
                               maxItems: 8
                               type: array
                             cost:
@@ -1199,12 +1260,12 @@ spec:
                                   RateLimitSelectCondition specifies the attributes within the traffic flow that can
                                   be used to select a subset of clients to be ratelimited.
                                   All the individual conditions must hold True for the overall condition to hold True.
+                                  And, at least one of headers or methods or path or sourceCIDR condition must be specified.
                                 properties:
                                   headers:
                                     description: |-
                                       Headers is a list of request headers to match. Multiple header values are ANDed together,
                                       meaning, a request MUST match all the specified headers.
-                                      At least one of headers or sourceCIDR condition must be specified.
                                     items:
                                       description: HeaderMatch defines the match attributes
                                         within the HTTP Headers of the request.
@@ -1245,10 +1306,66 @@ spec:
                                       type: object
                                     maxItems: 16
                                     type: array
-                                  sourceCIDR:
+                                  methods:
+                                    description: |-
+                                      Methods is a list of request methods to match. Multiple method values are ORed together,
+                                      meaning, a request can match any one of the specified methods. If not specified, it matches all methods.
+                                    items:
+                                      description: MethodMatch defines the matching
+                                        criteria for the HTTP method of a request.
+                                      properties:
+                                        invert:
+                                          default: false
+                                          description: Invert specifies whether the
+                                            value match result will be inverted.
+                                          type: boolean
+                                        value:
+                                          description: Value specifies the HTTP method.
+                                          enum:
+                                          - GET
+                                          - HEAD
+                                          - POST
+                                          - PUT
+                                          - DELETE
+                                          - CONNECT
+                                          - OPTIONS
+                                          - TRACE
+                                          - PATCH
+                                          type: string
+                                      required:
+                                      - value
+                                      type: object
+                                    type: array
+                                  path:
                                     description: |-
-                                      SourceCIDR is the client IP Address range to match on.
-                                      At least one of headers or sourceCIDR condition must be specified.
+                                      Path is the request path to match.
+                                      Support Exact, PathPrefix and RegularExpression match types.
+                                    properties:
+                                      invert:
+                                        default: false
+                                        description: Invert specifies whether the
+                                          value match result will be inverted.
+                                        type: boolean
+                                      type:
+                                        default: PathPrefix
+                                        description: Type specifies how to match against
+                                          the value of the path.
+                                        enum:
+                                        - Exact
+                                        - PathPrefix
+                                        - RegularExpression
+                                        type: string
+                                      value:
+                                        default: /
+                                        description: Value specifies the HTTP path.
+                                        maxLength: 1024
+                                        type: string
+                                    required:
+                                    - value
+                                    type: object
+                                  sourceCIDR:
+                                    description: SourceCIDR is the client IP Address
+                                      range to match on.
                                     properties:
                                       type:
                                         default: Exact
@@ -1268,6 +1385,11 @@ spec:
                                     - value
                                     type: object
                                 type: object
+                                x-kubernetes-validations:
+                                - message: at least one of headers, methods, path
+                                    or sourceCIDR must be specified
+                                  rule: has(self.headers) || has(self.methods) ||
+                                    has(self.path) || has(self.sourceCIDR)
                               maxItems: 8
                               type: array
                             cost:
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
index 125c4ddf8..99685fdd4 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
@@ -947,12 +947,12 @@ spec:
                                   RateLimitSelectCondition specifies the attributes within the traffic flow that can
                                   be used to select a subset of clients to be ratelimited.
                                   All the individual conditions must hold True for the overall condition to hold True.
+                                  And, at least one of headers or methods or path or sourceCIDR condition must be specified.
                                 properties:
                                   headers:
                                     description: |-
                                       Headers is a list of request headers to match. Multiple header values are ANDed together,
                                       meaning, a request MUST match all the specified headers.
-                                      At least one of headers or sourceCIDR condition must be specified.
                                     items:
                                       description: HeaderMatch defines the match attributes
                                         within the HTTP Headers of the request.
@@ -993,10 +993,66 @@ spec:
                                       type: object
                                     maxItems: 16
                                     type: array
-                                  sourceCIDR:
+                                  methods:
+                                    description: |-
+                                      Methods is a list of request methods to match. Multiple method values are ORed together,
+                                      meaning, a request can match any one of the specified methods. If not specified, it matches all methods.
+                                    items:
+                                      description: MethodMatch defines the matching
+                                        criteria for the HTTP method of a request.
+                                      properties:
+                                        invert:
+                                          default: false
+                                          description: Invert specifies whether the
+                                            value match result will be inverted.
+                                          type: boolean
+                                        value:
+                                          description: Value specifies the HTTP method.
+                                          enum:
+                                          - GET
+                                          - HEAD
+                                          - POST
+                                          - PUT
+                                          - DELETE
+                                          - CONNECT
+                                          - OPTIONS
+                                          - TRACE
+                                          - PATCH
+                                          type: string
+                                      required:
+                                      - value
+                                      type: object
+                                    type: array
+                                  path:
                                     description: |-
-                                      SourceCIDR is the client IP Address range to match on.
-                                      At least one of headers or sourceCIDR condition must be specified.
+                                      Path is the request path to match.
+                                      Support Exact, PathPrefix and RegularExpression match types.
+                                    properties:
+                                      invert:
+                                        default: false
+                                        description: Invert specifies whether the
+                                          value match result will be inverted.
+                                        type: boolean
+                                      type:
+                                        default: PathPrefix
+                                        description: Type specifies how to match against
+                                          the value of the path.
+                                        enum:
+                                        - Exact
+                                        - PathPrefix
+                                        - RegularExpression
+                                        type: string
+                                      value:
+                                        default: /
+                                        description: Value specifies the HTTP path.
+                                        maxLength: 1024
+                                        type: string
+                                    required:
+                                    - value
+                                    type: object
+                                  sourceCIDR:
+                                    description: SourceCIDR is the client IP Address
+                                      range to match on.
                                     properties:
                                       type:
                                         default: Exact
@@ -1016,6 +1072,11 @@ spec:
                                     - value
                                     type: object
                                 type: object
+                                x-kubernetes-validations:
+                                - message: at least one of headers, methods, path
+                                    or sourceCIDR must be specified
+                                  rule: has(self.headers) || has(self.methods) ||
+                                    has(self.path) || has(self.sourceCIDR)
                               maxItems: 8
                               type: array
                             cost:
@@ -1198,12 +1259,12 @@ spec:
                                   RateLimitSelectCondition specifies the attributes within the traffic flow that can
                                   be used to select a subset of clients to be ratelimited.
                                   All the individual conditions must hold True for the overall condition to hold True.
+                                  And, at least one of headers or methods or path or sourceCIDR condition must be specified.
                                 properties:
                                   headers:
                                     description: |-
                                       Headers is a list of request headers to match. Multiple header values are ANDed together,
                                       meaning, a request MUST match all the specified headers.
-                                      At least one of headers or sourceCIDR condition must be specified.
                                     items:
                                       description: HeaderMatch defines the match attributes
                                         within the HTTP Headers of the request.
@@ -1244,10 +1305,66 @@ spec:
                                       type: object
                                     maxItems: 16
                                     type: array
-                                  sourceCIDR:
+                                  methods:
+                                    description: |-
+                                      Methods is a list of request methods to match. Multiple method values are ORed together,
+                                      meaning, a request can match any one of the specified methods. If not specified, it matches all methods.
+                                    items:
+                                      description: MethodMatch defines the matching
+                                        criteria for the HTTP method of a request.
+                                      properties:
+                                        invert:
+                                          default: false
+                                          description: Invert specifies whether the
+                                            value match result will be inverted.
+                                          type: boolean
+                                        value:
+                                          description: Value specifies the HTTP method.
+                                          enum:
+                                          - GET
+                                          - HEAD
+                                          - POST
+                                          - PUT
+                                          - DELETE
+                                          - CONNECT
+                                          - OPTIONS
+                                          - TRACE
+                                          - PATCH
+                                          type: string
+                                      required:
+                                      - value
+                                      type: object
+                                    type: array
+                                  path:
                                     description: |-
-                                      SourceCIDR is the client IP Address range to match on.
-                                      At least one of headers or sourceCIDR condition must be specified.
+                                      Path is the request path to match.
+                                      Support Exact, PathPrefix and RegularExpression match types.
+                                    properties:
+                                      invert:
+                                        default: false
+                                        description: Invert specifies whether the
+                                          value match result will be inverted.
+                                        type: boolean
+                                      type:
+                                        default: PathPrefix
+                                        description: Type specifies how to match against
+                                          the value of the path.
+                                        enum:
+                                        - Exact
+                                        - PathPrefix
+                                        - RegularExpression
+                                        type: string
+                                      value:
+                                        default: /
+                                        description: Value specifies the HTTP path.
+                                        maxLength: 1024
+                                        type: string
+                                    required:
+                                    - value
+                                    type: object
+                                  sourceCIDR:
+                                    description: SourceCIDR is the client IP Address
+                                      range to match on.
                                     properties:
                                       type:
                                         default: Exact
@@ -1267,6 +1384,11 @@ spec:
                                     - value
                                     type: object
                                 type: object
+                                x-kubernetes-validations:
+                                - message: at least one of headers, methods, path
+                                    or sourceCIDR must be specified
+                                  rule: has(self.headers) || has(self.methods) ||
+                                    has(self.path) || has(self.sourceCIDR)
                               maxItems: 8
                               type: array
                             cost:
diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go
index 801dbefcd..76a510143 100644
--- a/internal/gatewayapi/backendtrafficpolicy.go
+++ b/internal/gatewayapi/backendtrafficpolicy.go
@@ -887,14 +887,16 @@ func buildRateLimitRule(rule egv1a1.RateLimitRule) (*ir.RateLimitRule, error) {
 			Unit:     ir.RateLimitUnit(rule.Limit.Unit),
 		},
 		HeaderMatches: make([]*ir.StringMatch, 0),
+		MethodMatches: make([]*ir.StringMatch, 0),
 		Shared:        rule.Shared,
 	}
 
 	for _, match := range rule.ClientSelectors {
-		if len(match.Headers) == 0 && match.SourceCIDR == nil {
+		if len(match.Headers) == 0 && len(match.Methods) == 0 &&
+			match.Path == nil && match.SourceCIDR == nil {
 			return nil, fmt.Errorf(
 				"unable to translate rateLimit. At least one of the" +
-					" header or sourceCIDR must be specified")
+					" header or method or path or sourceCIDR must be specified")
 		}
 		for _, header := range match.Headers {
 			switch {
@@ -934,6 +936,38 @@ func buildRateLimitRule(rule egv1a1.RateLimitRule) (*ir.RateLimitRule, error) {
 			}
 		}
 
+		for _, method := range match.Methods {
+			irRule.MethodMatches = append(irRule.MethodMatches, &ir.StringMatch{
+				Exact:  ptr.To(string(method.Value)),
+				Invert: method.Invert,
+			})
+		}
+
+		if match.Path != nil {
+			switch ptr.Deref(match.Path.Type, gwapiv1.PathMatchPathPrefix) {
+			case gwapiv1.PathMatchPathPrefix:
+				irRule.PathMatch = &ir.StringMatch{
+					Prefix: ptr.To(match.Path.Value),
+					Invert: match.Path.Invert,
+				}
+			case gwapiv1.PathMatchExact:
+				irRule.PathMatch = &ir.StringMatch{
+					Exact:  ptr.To(match.Path.Value),
+					Invert: match.Path.Invert,
+				}
+			case gwapiv1.PathMatchRegularExpression:
+				if err := regex.Validate(match.Path.Value); err != nil {
+					return nil, err
+				}
+				irRule.PathMatch = &ir.StringMatch{
+					SafeRegex: ptr.To(match.Path.Value),
+					Invert:    match.Path.Invert,
+				}
+			default:
+				return nil, fmt.Errorf("unable to translate rateLimit: invalid path type.")
+			}
+		}
+
 		if match.SourceCIDR != nil {
 			// distinct means that each IP Address within the specified Source IP CIDR is treated as a
 			// distinct client selector and uses a separate rate limit bucket/counter.
diff --git a/internal/ir/xds.go b/internal/ir/xds.go
index 6fd5106a3..81b85696c 100644
--- a/internal/ir/xds.go
+++ b/internal/ir/xds.go
@@ -2240,6 +2240,10 @@ type LocalRateLimit struct {
 type RateLimitRule struct {
 	// HeaderMatches define the match conditions on the request headers for this route.
 	HeaderMatches []*StringMatch `json:"headerMatches" yaml:"headerMatches"`
+	// PathMatch defines the match conditions on the request path for this route.
+	PathMatch *StringMatch `json:"pathMatch,omitempty" yaml:"pathMatch,omitempty"`
+	// MethodMatches define the match conditions on the request methods for this route.
+	MethodMatches []*StringMatch `json:"methodMatches,omitempty" yaml:"methodMatches,omitempty"`
 	// CIDRMatch define the match conditions on the source IP's CIDR for this route.
 	CIDRMatch *CIDRMatch `json:"cidrMatch,omitempty" yaml:"cidrMatch,omitempty"`
 	// Limit holds the rate limit values.
@@ -2278,7 +2282,7 @@ type CIDRMatch struct {
 
 // TODO zhaohuabing: remove this function
 func (r *RateLimitRule) IsMatchSet() bool {
-	return len(r.HeaderMatches) != 0 || r.CIDRMatch != nil
+	return len(r.HeaderMatches) != 0 || r.PathMatch != nil || len(r.MethodMatches) != 0 || r.CIDRMatch != nil
 }
 
 type RateLimitUnit egv1a1.RateLimitUnit
diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go
index 217ca07fc..c9b85c465 100644
--- a/internal/ir/zz_generated.deepcopy.go
+++ b/internal/ir/zz_generated.deepcopy.go
@@ -3064,6 +3064,22 @@ func (in *RateLimitRule) DeepCopyInto(out *RateLimitRule) {
 			}
 		}
 	}
+	if in.PathMatch != nil {
+		in, out := &in.PathMatch, &out.PathMatch
+		*out = new(StringMatch)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.MethodMatches != nil {
+		in, out := &in.MethodMatches, &out.MethodMatches
+		*out = make([]*StringMatch, len(*in))
+		for i := range *in {
+			if (*in)[i] != nil {
+				in, out := &(*in)[i], &(*out)[i]
+				*out = new(StringMatch)
+				(*in).DeepCopyInto(*out)
+			}
+		}
+	}
 	if in.CIDRMatch != nil {
 		in, out := &in.CIDRMatch, &out.CIDRMatch
 		*out = new(CIDRMatch)
diff --git a/internal/xds/translator/local_ratelimit.go b/internal/xds/translator/local_ratelimit.go
index 7a7d16ab1..369712e20 100644
--- a/internal/xds/translator/local_ratelimit.go
+++ b/internal/xds/translator/local_ratelimit.go
@@ -203,123 +203,231 @@ func buildRouteLocalRateLimits(local *ir.LocalRateLimit) (
 
 	// Rules are ORed
 	for rIdx, rule := range local.Rules {
-		var rlActions []*routev3.RateLimit_Action
-		var descriptorEntries []*rlv3.RateLimitDescriptor_Entry
-
-		// HeaderMatches
-		for mIdx, match := range rule.HeaderMatches {
-			var action *routev3.RateLimit_Action
-			var entry *rlv3.RateLimitDescriptor_Entry
-
-			if match.Distinct {
-				// For distinct matches, we only check if the header exists using the RequestHeaders action.
-				descriptorKey := getRouteRuleDescriptor(rIdx, mIdx)
-				action = &routev3.RateLimit_Action{
-					ActionSpecifier: &routev3.RateLimit_Action_RequestHeaders_{
-						RequestHeaders: &routev3.RateLimit_Action_RequestHeaders{
-							HeaderName:    match.Name,
-							DescriptorKey: descriptorKey,
-						},
-					},
-				}
-				// The descriptor entry value is not set for distinct matches, which means that each distinct
-				// value of the matched header will be counted separately.
-				entry = &rlv3.RateLimitDescriptor_Entry{
-					Key: descriptorKey,
-				}
-			} else {
-				// For exact matches, we check if there is an existing header with the matching value using the
-				// HeaderValueMatch action.
-				descriptorKey := getRouteRuleDescriptor(rIdx, mIdx)
-				descriptorVal := getRouteRuleDescriptor(rIdx, mIdx)
-				headerMatcher := &routev3.HeaderMatcher{
-					Name: match.Name,
-					HeaderMatchSpecifier: &routev3.HeaderMatcher_StringMatch{
-						StringMatch: buildXdsStringMatcher(match),
-					},
-				}
-				expectMatch := true
-				if match.Invert != nil && *match.Invert {
-					expectMatch = false
-				}
-				action = &routev3.RateLimit_Action{
-					ActionSpecifier: &routev3.RateLimit_Action_HeaderValueMatch_{
-						HeaderValueMatch: &routev3.RateLimit_Action_HeaderValueMatch{
-							DescriptorKey:   descriptorKey,
-							DescriptorValue: descriptorVal,
-							ExpectMatch: &wrapperspb.BoolValue{
-								Value: expectMatch,
-							},
-							Headers: []*routev3.HeaderMatcher{headerMatcher},
-						},
+		// If method matches specified, create one rate limit rule per method (OR behavior),
+		// these rules share the same limit counter, so they share the same descriptor.
+		methodMatches := rule.MethodMatches
+		if len(methodMatches) == 0 {
+			// Use nil to indicate no method match
+			methodMatches = []*ir.StringMatch{nil}
+		}
+
+		for _, methodMatch := range methodMatches {
+			var rlActions []*routev3.RateLimit_Action
+			var descriptorEntries []*rlv3.RateLimitDescriptor_Entry
+
+			// Build all match conditions
+			// - HeaderMatch
+			// - MethodMatch
+			// - PathMatch
+			// - CIDRMatch
+			buildHeaderMatchLocalRateLimitActions(&rlActions, &descriptorEntries, rIdx, rule.HeaderMatches)
+			buildMethodMatchLocalRateLimitAction(&rlActions, &descriptorEntries, rIdx, methodMatch)
+			buildPathMatchLocalRateLimitAction(&rlActions, &descriptorEntries, rIdx, rule.PathMatch)
+			buildCIDRMatchLocalRateLimitActions(&rlActions, &descriptorEntries, rule.CIDRMatch)
+
+			// Create rate limit and descriptor
+			rateLimits = append(rateLimits, &routev3.RateLimit{Actions: rlActions})
+			descriptors = append(descriptors, &rlv3.LocalRateLimitDescriptor{
+				Entries: descriptorEntries,
+				TokenBucket: &typev3.TokenBucket{
+					MaxTokens: uint32(rule.Limit.Requests),
+					TokensPerFill: &wrapperspb.UInt32Value{
+						Value: uint32(rule.Limit.Requests),
 					},
-				}
-				// For exact matches, the descriptor entry value is set to the generated descriptor value.
-				entry = &rlv3.RateLimitDescriptor_Entry{
-					Key:   descriptorKey,
-					Value: descriptorVal,
-				}
-			}
-			rlActions = append(rlActions, action)
-			descriptorEntries = append(descriptorEntries, entry)
+					FillInterval: ratelimit.UnitToDuration(rule.Limit.Unit),
+				},
+			})
 		}
+	}
 
-		// Source IP CIDRMatch
-		if rule.CIDRMatch != nil {
-			// For CIDR matches, we first need to check if the source IP matches the CIDR range using
-			// the MaskedRemoteAddress action.
-			mra := &routev3.RateLimit_Action_MaskedRemoteAddress{}
-			maskLen := &wrapperspb.UInt32Value{Value: rule.CIDRMatch.MaskLen}
-			if rule.CIDRMatch.IsIPv6 {
-				mra.V6PrefixMaskLen = maskLen
-			} else {
-				mra.V4PrefixMaskLen = maskLen
-			}
-			action := &routev3.RateLimit_Action{
-				ActionSpecifier: &routev3.RateLimit_Action_MaskedRemoteAddress_{
-					MaskedRemoteAddress: mra,
+	return rateLimits, descriptors
+}
+
+func buildHeaderMatchLocalRateLimitActions(
+	rlActions *[]*routev3.RateLimit_Action,
+	descriptorEntries *[]*rlv3.RateLimitDescriptor_Entry,
+	ruleIdx int,
+	headerMatches []*ir.StringMatch,
+) {
+	for mIdx, match := range headerMatches {
+		var action *routev3.RateLimit_Action
+		var entry *rlv3.RateLimitDescriptor_Entry
+
+		if match.Distinct {
+			// For distinct matches, we only check if the header exists using the RequestHeaders action.
+			descriptorKey := getRouteRuleDescriptor(ruleIdx, mIdx)
+			action = &routev3.RateLimit_Action{
+				ActionSpecifier: &routev3.RateLimit_Action_RequestHeaders_{
+					RequestHeaders: &routev3.RateLimit_Action_RequestHeaders{
+						HeaderName:    match.Name,
+						DescriptorKey: descriptorKey,
+					},
 				},
 			}
-			entry := &rlv3.RateLimitDescriptor_Entry{
-				Key:   descriptorMaskedRemoteAddress,
-				Value: rule.CIDRMatch.CIDR,
+			// The descriptor entry value is not set for distinct matches, which means that each distinct
+			// value of the matched header will be counted separately.
+			entry = &rlv3.RateLimitDescriptor_Entry{
+				Key: descriptorKey,
+			}
+		} else {
+			// For exact matches, we check if there is an existing header with the matching value using the
+			// HeaderValueMatch action.
+			descriptorKey := getRouteRuleDescriptor(ruleIdx, mIdx)
+			descriptorVal := getRouteRuleDescriptor(ruleIdx, mIdx)
+			headerMatcher := &routev3.HeaderMatcher{
+				Name: match.Name,
+				HeaderMatchSpecifier: &routev3.HeaderMatcher_StringMatch{
+					StringMatch: buildXdsStringMatcher(match),
+				},
 			}
-			descriptorEntries = append(descriptorEntries, entry)
-			rlActions = append(rlActions, action)
-
-			if rule.CIDRMatch.Distinct {
-				// If the CIDRMatch is distinct, we also need to use the RemoteAddress action to get the client IP.
-				action = &routev3.RateLimit_Action{
-					ActionSpecifier: &routev3.RateLimit_Action_RemoteAddress_{
-						RemoteAddress: &routev3.RateLimit_Action_RemoteAddress{},
+			expectMatch := match.Invert == nil || !*match.Invert
+			action = &routev3.RateLimit_Action{
+				ActionSpecifier: &routev3.RateLimit_Action_HeaderValueMatch_{
+					HeaderValueMatch: &routev3.RateLimit_Action_HeaderValueMatch{
+						DescriptorKey:   descriptorKey,
+						DescriptorValue: descriptorVal,
+						ExpectMatch: &wrapperspb.BoolValue{
+							Value: expectMatch,
+						},
+						Headers: []*routev3.HeaderMatcher{headerMatcher},
 					},
-				}
-
-				// If the CIDRMatch is distinct, we use the built-in remote address descriptor key without a value.
-				// This means that each distinct client IP will be counted separately.
-				entry = &rlv3.RateLimitDescriptor_Entry{
-					Key: descriptorRemoteAddress,
-				}
-				descriptorEntries = append(descriptorEntries, entry)
-				rlActions = append(rlActions, action)
+				},
+			}
+			// For exact matches, the descriptor entry value is set to the generated descriptor value.
+			entry = &rlv3.RateLimitDescriptor_Entry{
+				Key:   descriptorKey,
+				Value: descriptorVal,
 			}
 		}
+		*rlActions = append(*rlActions, action)
+		*descriptorEntries = append(*descriptorEntries, entry)
+	}
+}
 
-		rateLimit := &routev3.RateLimit{Actions: rlActions}
-		rateLimits = append(rateLimits, rateLimit)
+func buildPathMatchLocalRateLimitAction(
+	rlActions *[]*routev3.RateLimit_Action,
+	descriptorEntries *[]*rlv3.RateLimitDescriptor_Entry,
+	ruleIdx int,
+	pathMatch *ir.StringMatch,
+) {
+	if pathMatch == nil {
+		return
+	}
 
-		descriptor := &rlv3.LocalRateLimitDescriptor{
-			Entries: descriptorEntries,
-			TokenBucket: &typev3.TokenBucket{
-				MaxTokens: uint32(rule.Limit.Requests),
-				TokensPerFill: &wrapperspb.UInt32Value{
-					Value: uint32(rule.Limit.Requests),
+	descriptorKey := getRouteRulePathDescriptor(ruleIdx)
+	descriptorVal := getRouteRulePathDescriptor(ruleIdx)
+	headerMatcher := &routev3.HeaderMatcher{
+		Name: ":path",
+		HeaderMatchSpecifier: &routev3.HeaderMatcher_StringMatch{
+			StringMatch: buildXdsStringMatcher(pathMatch),
+		},
+	}
+	expectMatch := pathMatch.Invert == nil || !*pathMatch.Invert
+	action := &routev3.RateLimit_Action{
+		ActionSpecifier: &routev3.RateLimit_Action_HeaderValueMatch_{
+			HeaderValueMatch: &routev3.RateLimit_Action_HeaderValueMatch{
+				DescriptorKey:   descriptorKey,
+				DescriptorValue: descriptorVal,
+				ExpectMatch: &wrapperspb.BoolValue{
+					Value: expectMatch,
 				},
-				FillInterval: ratelimit.UnitToDuration(rule.Limit.Unit),
+				Headers: []*routev3.HeaderMatcher{headerMatcher},
 			},
+		},
+	}
+	entry := &rlv3.RateLimitDescriptor_Entry{
+		Key:   descriptorKey,
+		Value: descriptorVal,
+	}
+
+	*rlActions = append(*rlActions, action)
+	*descriptorEntries = append(*descriptorEntries, entry)
+}
+
+func buildCIDRMatchLocalRateLimitActions(
+	rlActions *[]*routev3.RateLimit_Action,
+	descriptorEntries *[]*rlv3.RateLimitDescriptor_Entry,
+	cidrMatch *ir.CIDRMatch,
+) {
+	if cidrMatch == nil {
+		return
+	}
+
+	// For CIDR matches, we first need to check if the source IP matches the CIDR range using
+	// the MaskedRemoteAddress action.
+	mra := &routev3.RateLimit_Action_MaskedRemoteAddress{}
+	maskLen := &wrapperspb.UInt32Value{Value: cidrMatch.MaskLen}
+	if cidrMatch.IsIPv6 {
+		mra.V6PrefixMaskLen = maskLen
+	} else {
+		mra.V4PrefixMaskLen = maskLen
+	}
+	action := &routev3.RateLimit_Action{
+		ActionSpecifier: &routev3.RateLimit_Action_MaskedRemoteAddress_{
+			MaskedRemoteAddress: mra,
+		},
+	}
+	entry := &rlv3.RateLimitDescriptor_Entry{
+		Key:   descriptorMaskedRemoteAddress,
+		Value: cidrMatch.CIDR,
+	}
+	*descriptorEntries = append(*descriptorEntries, entry)
+	*rlActions = append(*rlActions, action)
+
+	if cidrMatch.Distinct {
+		// If the CIDRMatch is distinct, we also need to use the RemoteAddress action to get the client IP.
+		action = &routev3.RateLimit_Action{
+			ActionSpecifier: &routev3.RateLimit_Action_RemoteAddress_{
+				RemoteAddress: &routev3.RateLimit_Action_RemoteAddress{},
+			},
+		}
+
+		// If the CIDRMatch is distinct, we use the built-in remote address descriptor key without a value.
+		// This means that each distinct client IP will be counted separately.
+		entry = &rlv3.RateLimitDescriptor_Entry{
+			Key: descriptorRemoteAddress,
 		}
-		descriptors = append(descriptors, descriptor)
+		*descriptorEntries = append(*descriptorEntries, entry)
+		*rlActions = append(*rlActions, action)
 	}
+}
 
-	return rateLimits, descriptors
+func buildMethodMatchLocalRateLimitAction(
+	rlActions *[]*routev3.RateLimit_Action,
+	descriptorEntries *[]*rlv3.RateLimitDescriptor_Entry,
+	ruleIdx int,
+	methodMatch *ir.StringMatch,
+) {
+	if methodMatch == nil {
+		return
+	}
+
+	descriptorKey := getRouteRuleMethodDescriptor(ruleIdx)
+	descriptorVal := getRouteRuleMethodDescriptor(ruleIdx)
+	headerMatcher := &routev3.HeaderMatcher{
+		Name: ":method",
+		HeaderMatchSpecifier: &routev3.HeaderMatcher_StringMatch{
+			StringMatch: buildXdsStringMatcher(methodMatch),
+		},
+	}
+	expectMatch := methodMatch.Invert == nil || !*methodMatch.Invert
+	action := &routev3.RateLimit_Action{
+		ActionSpecifier: &routev3.RateLimit_Action_HeaderValueMatch_{
+			HeaderValueMatch: &routev3.RateLimit_Action_HeaderValueMatch{
+				DescriptorKey:   descriptorKey,
+				DescriptorValue: descriptorVal,
+				ExpectMatch: &wrapperspb.BoolValue{
+					Value: expectMatch,
+				},
+				Headers: []*routev3.HeaderMatcher{headerMatcher},
+			},
+		},
+	}
+	entry := &rlv3.RateLimitDescriptor_Entry{
+		Key:   descriptorKey,
+		Value: descriptorVal,
+	}
+
+	*rlActions = append(*rlActions, action)
+	*descriptorEntries = append(*descriptorEntries, entry)
 }
diff --git a/internal/xds/translator/ratelimit.go b/internal/xds/translator/ratelimit.go
index be4570948..54257c48e 100644
--- a/internal/xds/translator/ratelimit.go
+++ b/internal/xds/translator/ratelimit.go
@@ -213,164 +213,263 @@ func buildRouteRateLimits(route *ir.HTTPRoute) (rateLimits []*routev3.RateLimit,
 
 	// Iterate over each rule in the global rate limit configuration.
 	for rIdx, rule := range global.Rules {
-		// Create a list of rate limit actions for the current rule.
-		var rlActions []*routev3.RateLimit_Action
-
-		// Create the route descriptor using the rule's shared attribute
-		var descriptorKey, descriptorValue string
-		if isRuleShared(rule) {
-			// For shared rule, use full rule name
-			descriptorKey = rule.Name
-			descriptorValue = rule.Name
-		} else {
-			// For non-shared rule, use route name in descriptor
-			descriptorKey = getRouteDescriptor(route.Name)
-			descriptorValue = descriptorKey
+		// If method matches specified, create one rate limit rule per method (OR behavior),
+		// these rules share the same limit counter, so they share the same descriptor.
+		methodMatches := rule.MethodMatches
+		if len(methodMatches) == 0 {
+			// Use nil to indicate no method match
+			methodMatches = []*ir.StringMatch{nil}
 		}
 
-		// Create a generic key action for the route descriptor.
-		routeDescriptor := &routev3.RateLimit_Action{
-			ActionSpecifier: &routev3.RateLimit_Action_GenericKey_{
-				GenericKey: &routev3.RateLimit_Action_GenericKey{
-					DescriptorKey:   descriptorKey,
-					DescriptorValue: descriptorValue,
-				},
-			},
-		}
-
-		// Add the generic key action
-		rlActions = append(rlActions, routeDescriptor)
-
-		// Calculate the domain-specific rule index (0-based for each domain)
-		ruleIsShared := isRuleShared(rule)
-		domainRuleIdx := getDomainRuleIndex(global.Rules, rIdx, ruleIsShared)
-
-		// Process each header match in the rule.
-		for mIdx, match := range rule.HeaderMatches {
-			var action *routev3.RateLimit_Action
+		for _, methodMatch := range methodMatches {
+			// Create a list of rate limit actions for the current rule.
+			var rlActions []*routev3.RateLimit_Action
 
-			// Handle distinct matches by setting up request header actions.
-			if match.Distinct {
-				descriptorKey := getRouteRuleDescriptor(domainRuleIdx, mIdx)
-				action = &routev3.RateLimit_Action{
-					ActionSpecifier: &routev3.RateLimit_Action_RequestHeaders_{
-						RequestHeaders: &routev3.RateLimit_Action_RequestHeaders{
-							HeaderName:    match.Name,
-							DescriptorKey: descriptorKey,
-						},
-					},
-				}
+			// Create the route descriptor using the rule's shared attribute
+			var descriptorKey, descriptorValue string
+			if isRuleShared(rule) {
+				// For shared rule, use full rule name
+				descriptorKey = rule.Name
+				descriptorValue = rule.Name
 			} else {
-				// Handle non-distinct matches by setting up header value match actions.
-				descriptorKey := getRouteRuleDescriptor(domainRuleIdx, mIdx)
-				descriptorVal := getRouteRuleDescriptor(domainRuleIdx, mIdx)
-				headerMatcher := &routev3.HeaderMatcher{
-					Name: match.Name,
-					HeaderMatchSpecifier: &routev3.HeaderMatcher_StringMatch{
-						StringMatch: buildXdsStringMatcher(match),
-					},
-				}
-				expectMatch := true
-				if match.Invert != nil && *match.Invert {
-					expectMatch = false
-				}
-				action = &routev3.RateLimit_Action{
-					ActionSpecifier: &routev3.RateLimit_Action_HeaderValueMatch_{
-						HeaderValueMatch: &routev3.RateLimit_Action_HeaderValueMatch{
-							DescriptorKey:   descriptorKey,
-							DescriptorValue: descriptorVal,
-							ExpectMatch: &wrapperspb.BoolValue{
-								Value: expectMatch,
-							},
-							Headers: []*routev3.HeaderMatcher{headerMatcher},
-						},
-					},
-				}
+				// For non-shared rule, use route name in descriptor
+				descriptorKey = getRouteDescriptor(route.Name)
+				descriptorValue = descriptorKey
 			}
-			// Add the action to the list of rate limit actions.
-			rlActions = append(rlActions, action)
-		}
 
-		// To be able to rate limit each individual IP, we need to use a nested descriptors structure in the configuration
-		// of the rate limit server:
-		// * the outer layer is a masked_remote_address descriptor that catches all the source IPs inside a specified CIDR.
-		// * the inner layer is a remote_address descriptor that sets the limit for individual IP.
-		//
-		// An example of rate limit server configuration looks like this:
-		//
-		//  descriptors:
-		//    - key: masked_remote_address //catch all the source IPs inside a CIDR
-		//      value: 192.168.0.0/16
-		//      descriptors:
-		//        - key: remote_address //set limit for individual IP
-		//          rate_limit:
-		//            unit: second
-		//            requests_per_unit: 100
-		//
-		// Please refer to [Rate Limit Service Descriptor list definition](https://github.com/envoyproxy/ratelimit#descriptor-list-definition) for details.
-		// If a CIDR match is specified, add MaskedRemoteAddress and RemoteAddress descriptors.
-		if rule.CIDRMatch != nil {
-			// Setup MaskedRemoteAddress action.
-			mra := &routev3.RateLimit_Action_MaskedRemoteAddress{}
-			maskLen := &wrapperspb.UInt32Value{Value: rule.CIDRMatch.MaskLen}
-			if rule.CIDRMatch.IsIPv6 {
-				mra.V6PrefixMaskLen = maskLen
-			} else {
-				mra.V4PrefixMaskLen = maskLen
-			}
-			action := &routev3.RateLimit_Action{
-				ActionSpecifier: &routev3.RateLimit_Action_MaskedRemoteAddress_{
-					MaskedRemoteAddress: mra,
+			// Create a generic key action for the route descriptor.
+			routeDescriptor := &routev3.RateLimit_Action{
+				ActionSpecifier: &routev3.RateLimit_Action_GenericKey_{
+					GenericKey: &routev3.RateLimit_Action_GenericKey{
+						DescriptorKey:   descriptorKey,
+						DescriptorValue: descriptorValue,
+					},
 				},
 			}
-			rlActions = append(rlActions, action)
 
-			// Setup RemoteAddress action if distinct match is set.
-			if rule.CIDRMatch.Distinct {
-				action = &routev3.RateLimit_Action{
-					ActionSpecifier: &routev3.RateLimit_Action_RemoteAddress_{
-						RemoteAddress: &routev3.RateLimit_Action_RemoteAddress{},
+			// Add the generic key action
+			rlActions = append(rlActions, routeDescriptor)
+
+			// Calculate the domain-specific rule index (0-based for each domain)
+			ruleIsShared := isRuleShared(rule)
+			domainRuleIdx := getDomainRuleIndex(global.Rules, rIdx, ruleIsShared)
+
+			// Process each header match in the rule.
+			buildHeaderMatchRateLimitActions(&rlActions, domainRuleIdx, rule.HeaderMatches)
+			// Process each method match in the rule.
+			buildMethodMatchRateLimitActions(&rlActions, domainRuleIdx, methodMatch)
+			// Process each path match in the rule.
+			buildPathMatchRateLimitActions(&rlActions, domainRuleIdx, rule.PathMatch)
+			// Process each CIDR match in the rule.
+			buildCIDRMatchRateLimitActions(&rlActions, rule.CIDRMatch)
+
+			// Case when both header/method/path and cidr match are not set and the ratelimit
+			// will be applied to all traffic.
+			// 3) No Match (apply to all traffic)
+			if !rule.IsMatchSet() {
+				action := &routev3.RateLimit_Action{
+					ActionSpecifier: &routev3.RateLimit_Action_GenericKey_{
+						GenericKey: &routev3.RateLimit_Action_GenericKey{
+							DescriptorKey:   getRouteRuleDescriptor(domainRuleIdx, -1),
+							DescriptorValue: getRouteRuleDescriptor(domainRuleIdx, -1),
+						},
 					},
 				}
 				rlActions = append(rlActions, action)
 			}
+
+			// Create a rate limit object for the current rule.
+			rateLimit := &routev3.RateLimit{Actions: rlActions}
+
+			if c := rule.RequestCost; c != nil {
+				// Set the hits addend for the request cost if specified.
+				rateLimit.HitsAddend = rateLimitCostToHitsAddend(c)
+				costSpecified = true
+			}
+			// Add the rate limit to the list of rate limits.
+			rateLimits = append(rateLimits, rateLimit)
+
+			// Handle response cost by creating a separate rate limit object.
+			if c := rule.ResponseCost; c != nil {
+				responseRule := &routev3.RateLimit{Actions: rlActions, ApplyOnStreamDone: true}
+				responseRule.HitsAddend = rateLimitCostToHitsAddend(c)
+				rateLimits = append(rateLimits, responseRule)
+				costSpecified = true
+			}
 		}
+	}
+	return
+}
 
-		// Case when both header and cidr match are not set and the ratelimit
-		// will be applied to all traffic.
-		// 3) No Match (apply to all traffic)
-		if !rule.IsMatchSet() {
-			action := &routev3.RateLimit_Action{
-				ActionSpecifier: &routev3.RateLimit_Action_GenericKey_{
-					GenericKey: &routev3.RateLimit_Action_GenericKey{
-						DescriptorKey:   getRouteRuleDescriptor(domainRuleIdx, -1),
-						DescriptorValue: getRouteRuleDescriptor(domainRuleIdx, -1),
+func buildHeaderMatchRateLimitActions(
+	rlActions *[]*routev3.RateLimit_Action,
+	ruleIdx int,
+	headerMatches []*ir.StringMatch,
+) {
+	for mIdx, match := range headerMatches {
+		var action *routev3.RateLimit_Action
+
+		// Handle distinct matches by setting up request header actions.
+		if match.Distinct {
+			descriptorKey := getRouteRuleDescriptor(ruleIdx, mIdx)
+			action = &routev3.RateLimit_Action{
+				ActionSpecifier: &routev3.RateLimit_Action_RequestHeaders_{
+					RequestHeaders: &routev3.RateLimit_Action_RequestHeaders{
+						HeaderName:    match.Name,
+						DescriptorKey: descriptorKey,
+					},
+				},
+			}
+		} else {
+			// Handle non-distinct matches by setting up header value match actions.
+			descriptorKey := getRouteRuleDescriptor(ruleIdx, mIdx)
+			descriptorVal := getRouteRuleDescriptor(ruleIdx, mIdx)
+			headerMatcher := &routev3.HeaderMatcher{
+				Name: match.Name,
+				HeaderMatchSpecifier: &routev3.HeaderMatcher_StringMatch{
+					StringMatch: buildXdsStringMatcher(match),
+				},
+			}
+			expectMatch := true
+			if match.Invert != nil && *match.Invert {
+				expectMatch = false
+			}
+			action = &routev3.RateLimit_Action{
+				ActionSpecifier: &routev3.RateLimit_Action_HeaderValueMatch_{
+					HeaderValueMatch: &routev3.RateLimit_Action_HeaderValueMatch{
+						DescriptorKey:   descriptorKey,
+						DescriptorValue: descriptorVal,
+						ExpectMatch: &wrapperspb.BoolValue{
+							Value: expectMatch,
+						},
+						Headers: []*routev3.HeaderMatcher{headerMatcher},
 					},
 				},
 			}
-			rlActions = append(rlActions, action)
 		}
+		// Add the action to the list of rate limit actions.
+		*rlActions = append(*rlActions, action)
+	}
+}
 
-		// Create a rate limit object for the current rule.
-		rateLimit := &routev3.RateLimit{Actions: rlActions}
+func buildMethodMatchRateLimitActions(
+	rlActions *[]*routev3.RateLimit_Action,
+	ruleIdx int,
+	methodMatch *ir.StringMatch,
+) {
+	if methodMatch == nil {
+		return
+	}
 
-		if c := rule.RequestCost; c != nil {
-			// Set the hits addend for the request cost if specified.
-			rateLimit.HitsAddend = rateLimitCostToHitsAddend(c)
-			costSpecified = true
-		}
-		// Add the rate limit to the list of rate limits.
-		rateLimits = append(rateLimits, rateLimit)
-
-		// Handle response cost by creating a separate rate limit object.
-		if c := rule.ResponseCost; c != nil {
-			responseRule := &routev3.RateLimit{Actions: rlActions, ApplyOnStreamDone: true}
-			responseRule.HitsAddend = rateLimitCostToHitsAddend(c)
-			rateLimits = append(rateLimits, responseRule)
-			costSpecified = true
+	descriptorKey := getRouteRuleMethodDescriptor(ruleIdx)
+	descriptorVal := getRouteRuleMethodDescriptor(ruleIdx)
+	headerMatcher := &routev3.HeaderMatcher{
+		Name: ":method",
+		HeaderMatchSpecifier: &routev3.HeaderMatcher_StringMatch{
+			StringMatch: buildXdsStringMatcher(methodMatch),
+		},
+	}
+	expectMatch := methodMatch.Invert == nil || !*methodMatch.Invert
+	action := &routev3.RateLimit_Action{
+		ActionSpecifier: &routev3.RateLimit_Action_HeaderValueMatch_{
+			HeaderValueMatch: &routev3.RateLimit_Action_HeaderValueMatch{
+				DescriptorKey:   descriptorKey,
+				DescriptorValue: descriptorVal,
+				ExpectMatch: &wrapperspb.BoolValue{
+					Value: expectMatch,
+				},
+				Headers: []*routev3.HeaderMatcher{headerMatcher},
+			},
+		},
+	}
+	// Add the action to the list of rate limit actions.
+	*rlActions = append(*rlActions, action)
+}
+
+func buildPathMatchRateLimitActions(
+	rlActions *[]*routev3.RateLimit_Action,
+	ruleIdx int,
+	pathMatch *ir.StringMatch,
+) {
+	if pathMatch == nil {
+		return
+	}
+
+	descriptorKey := getRouteRulePathDescriptor(ruleIdx)
+	descriptorVal := getRouteRulePathDescriptor(ruleIdx)
+	headerMatcher := &routev3.HeaderMatcher{
+		Name: ":path",
+		HeaderMatchSpecifier: &routev3.HeaderMatcher_StringMatch{
+			StringMatch: buildXdsStringMatcher(pathMatch),
+		},
+	}
+	expectMatch := pathMatch.Invert == nil || !*pathMatch.Invert
+	action := &routev3.RateLimit_Action{
+		ActionSpecifier: &routev3.RateLimit_Action_HeaderValueMatch_{
+			HeaderValueMatch: &routev3.RateLimit_Action_HeaderValueMatch{
+				DescriptorKey:   descriptorKey,
+				DescriptorValue: descriptorVal,
+				ExpectMatch: &wrapperspb.BoolValue{
+					Value: expectMatch,
+				},
+				Headers: []*routev3.HeaderMatcher{headerMatcher},
+			},
+		},
+	}
+	// Add the action to the list of rate limit actions.
+	*rlActions = append(*rlActions, action)
+}
+
+// To be able to rate limit each individual IP, we need to use a nested descriptors structure in the configuration
+// of the rate limit server:
+// * the outer layer is a masked_remote_address descriptor that catches all the source IPs inside a specified CIDR.
+// * the inner layer is a remote_address descriptor that sets the limit for individual IP.
+//
+// An example of rate limit server configuration looks like this:
+//
+//	descriptors:
+//	  - key: masked_remote_address //catch all the source IPs inside a CIDR
+//	    value: 192.168.0.0/16
+//	    descriptors:
+//	      - key: remote_address //set limit for individual IP
+//	        rate_limit:
+//	          unit: second
+//	          requests_per_unit: 100
+//
+// Please refer to [Rate Limit Service Descriptor list definition](https://github.com/envoyproxy/ratelimit#descriptor-list-definition) for details.
+// If a CIDR match is specified, add MaskedRemoteAddress and RemoteAddress descriptors.
+func buildCIDRMatchRateLimitActions(
+	rlActions *[]*routev3.RateLimit_Action,
+	cidrMatch *ir.CIDRMatch,
+) {
+	if cidrMatch == nil {
+		return
+	}
+
+	// Setup MaskedRemoteAddress action.
+	mra := &routev3.RateLimit_Action_MaskedRemoteAddress{}
+	maskLen := &wrapperspb.UInt32Value{Value: cidrMatch.MaskLen}
+	if cidrMatch.IsIPv6 {
+		mra.V6PrefixMaskLen = maskLen
+	} else {
+		mra.V4PrefixMaskLen = maskLen
+	}
+	action := &routev3.RateLimit_Action{
+		ActionSpecifier: &routev3.RateLimit_Action_MaskedRemoteAddress_{
+			MaskedRemoteAddress: mra,
+		},
+	}
+	*rlActions = append(*rlActions, action)
+
+	// Setup RemoteAddress action if distinct match is set.
+	if cidrMatch.Distinct {
+		action = &routev3.RateLimit_Action{
+			ActionSpecifier: &routev3.RateLimit_Action_RemoteAddress_{
+				RemoteAddress: &routev3.RateLimit_Action_RemoteAddress{},
+			},
 		}
+		*rlActions = append(*rlActions, action)
 	}
-	return
 }
 
 func rateLimitCostToHitsAddend(c *ir.RateLimitCost) *routev3.RateLimit_HitsAddend {
@@ -597,8 +696,10 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi
 	// The order in which matching descriptors are built is consistent with
 	// the order in which ratelimit actions are built:
 	//  1) Header Matches
-	//  2) CIDR Match
-	//  3) No Match
+	//  2) Method Match
+	//  3) Path Match
+	//  4) CIDR Match
+	//  5) No Match
 
 	for rIdx, rule := range global.Rules {
 		rateLimitPolicy := &rlsconfv3.RateLimitPolicy{
@@ -635,6 +736,44 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi
 			cur = pbDesc
 
 			// Do not add the RateLimitPolicy to the last header match descriptor yet,
+			// as it is also possible that Method/Path/CIDR match descriptor also exist.
+		}
+
+		// 2) Method Match
+		if len(rule.MethodMatches) > 0 {
+			pbDesc := new(rlsconfv3.RateLimitDescriptor)
+			pbDesc.Key = getRouteRuleMethodDescriptor(domainRuleIdx)
+			pbDesc.Value = getRouteRuleMethodDescriptor(domainRuleIdx)
+
+			if cur != nil {
+				// The header match descriptor chain exist, add current
+				// descriptor to the chain.
+				cur.Descriptors = []*rlsconfv3.RateLimitDescriptor{pbDesc}
+			} else {
+				head = pbDesc
+			}
+			cur = pbDesc
+
+			// Do not add the RateLimitPolicy to method match descriptor yet,
+			// as it is also possible that Path/CIDR match descriptor also exist.
+		}
+
+		// 3) Path Match
+		if rule.PathMatch != nil {
+			pbDesc := new(rlsconfv3.RateLimitDescriptor)
+			pbDesc.Key = getRouteRulePathDescriptor(domainRuleIdx)
+			pbDesc.Value = getRouteRulePathDescriptor(domainRuleIdx)
+
+			if cur != nil {
+				// The header/method match descriptor chain exist, add current
+				// descriptor to the chain.
+				cur.Descriptors = []*rlsconfv3.RateLimitDescriptor{pbDesc}
+			} else {
+				head = pbDesc
+			}
+			cur = pbDesc
+
+			// Do not add the RateLimitPolicy to path match descriptor yet,
 			// as it is also possible that CIDR match descriptor also exist.
 		}
 
@@ -659,7 +798,7 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi
 		//            requests_per_unit: 100
 		//
 		// Please refer to [Rate Limit Service Descriptor list definition](https://github.com/envoyproxy/ratelimit#descriptor-list-definition) for details.
-		// 2) CIDR Match
+		// 4) CIDR Match
 		if rule.CIDRMatch != nil {
 			// MaskedRemoteAddress case
 			pbDesc := new(rlsconfv3.RateLimitDescriptor)
@@ -667,7 +806,7 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi
 			pbDesc.Value = rule.CIDRMatch.CIDR
 
 			if cur != nil {
-				// The header match descriptor chain exist, add current
+				// The header/method/path match descriptor chain exist, add current
 				// descriptor to the chain.
 				cur.Descriptors = []*rlsconfv3.RateLimitDescriptor{pbDesc}
 			} else {
@@ -682,7 +821,7 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi
 				cur = pbDesc
 			}
 		}
-		// Case when both header and cidr match are not set and the ratelimit
+		// Case when both header/method/path and cidr match are not set and the ratelimit
 		// will be applied to all traffic.
 		// 3) No Match (apply to all traffic)
 		if !rule.IsMatchSet() {
@@ -705,6 +844,14 @@ func getRouteRuleDescriptor(ruleIndex, matchIndex int) string {
 	return "rule-" + strconv.Itoa(ruleIndex) + "-match-" + strconv.Itoa(matchIndex)
 }
 
+func getRouteRuleMethodDescriptor(ruleIndex int) string {
+	return "rule-" + strconv.Itoa(ruleIndex) + "-method"
+}
+
+func getRouteRulePathDescriptor(ruleIndex int) string {
+	return "rule-" + strconv.Itoa(ruleIndex) + "-path"
+}
+
 func getRouteDescriptor(routeName string) string {
 	return routeName
 }
diff --git a/release-notes/current.yaml b/release-notes/current.yaml
index 9524ec001..e835c286f 100644
--- a/release-notes/current.yaml
+++ b/release-notes/current.yaml
@@ -29,6 +29,7 @@ new features: |
   Added support for Envoy PreconnectPolicy in BackendTrafficPolicy.
   Added support for binaryData in ConfigMap referenced by HTTPRouteFilter for direct response.
   Added support PDB for Ratelimit service.
+  Added support rate limit based on path and method in BackendTrafficPolicy.
 
 bug fixes: |
   Fixed %ROUTE_KIND% operator to be lower-cased when used by clusterStatName in EnvoyProxy API.
diff --git a/tools/make/docs.mk b/tools/make/docs.mk
index ade2d8ced..8d61f08af 100644
--- a/tools/make/docs.mk
+++ b/tools/make/docs.mk
@@ -4,7 +4,7 @@ RELEASE_VERSIONS ?= $(foreach v,$(wildcard ${ROOT_DIR}/docs/*),$(notdir ${v}))
 #       find a way to remove github.com from ignore list
 # TODO: example.com is not a valid domain, we should remove it from ignore list
 # TODO: https://www.gnu.org/software/make became unstable, we should remove it from ignore list later
-LINKINATOR_IGNORE := "opentelemetry.io github.com jwt.io githubusercontent.com example.com github.io gnu.org _print canva.com sched.co sap.com httpbin.org nemlig.com verve.com developer.hashicorp.com"
+LINKINATOR_IGNORE := "opentelemetry.io github.com jwt.io githubusercontent.com example.com github.io gnu.org _print canva.com sched.co sap.com httpbin.org nemlig.com verve.com developer.hashicorp.com gateway-api.sigs.k8s.io"
 CLEAN_NODE_MODULES ?= true
 
 ##@ Docs



Test patch:
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.in.yaml
index 678740706..74cae0fc6 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.in.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.in.yaml
@@ -27,6 +27,20 @@ gateways:
       allowedRoutes:
         namespaces:
           from: All
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    namespace: envoy-gateway
+    name: gateway-3
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - name: http
+      protocol: HTTP
+      port: 80
+      allowedRoutes:
+        namespaces:
+          from: All
 grpcRoutes:
 - apiVersion: gateway.networking.k8s.io/v1alpha2
   kind: GRPCRoute
@@ -42,6 +56,20 @@ grpcRoutes:
     - backendRefs:
       - name: service-1
         port: 8080
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: GRPCRoute
+  metadata:
+    namespace: default
+    name: grpcroute-2
+  spec:
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-3
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
 httpRoutes:
 - apiVersion: gateway.networking.k8s.io/v1
   kind: HTTPRoute
@@ -119,3 +147,27 @@ backendTrafficPolicies:
               metadata:
                 namespace: something.com
                 key: some_cost_set_by_foo
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: BackendTrafficPolicy
+  metadata:
+    namespace: default
+    name: policy-for-grpc-route
+  spec:
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: GRPCRoute
+      name: grpcroute-2
+    rateLimit:
+      type: Global
+      global:
+        rules:
+        - clientSelectors:
+          - path:
+              type: Exact
+              value: /test
+          - methods:
+            - value: GET
+              invert: true
+          limit:
+            requests: 30
+            unit: Hour
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml
index d69afacfc..ca29c7923 100644
--- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml
@@ -44,6 +44,45 @@ backendTrafficPolicies:
         status: "True"
         type: Accepted
       controllerName: gateway.envoyproxy.io/gatewayclass-controller
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: BackendTrafficPolicy
+  metadata:
+    name: policy-for-grpc-route
+    namespace: default
+  spec:
+    rateLimit:
+      global:
+        rules:
+        - clientSelectors:
+          - path:
+              type: Exact
+              value: /test
+          - methods:
+            - invert: true
+              value: GET
+          limit:
+            requests: 30
+            unit: Hour
+      type: Global
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: GRPCRoute
+      name: grpcroute-2
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-3
+        namespace: envoy-gateway
+        sectionName: http
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
 - apiVersion: gateway.envoyproxy.io/v1alpha1
   kind: BackendTrafficPolicy
   metadata:
@@ -163,6 +202,45 @@ gateways:
         kind: HTTPRoute
       - group: gateway.networking.k8s.io
         kind: GRPCRoute
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    name: gateway-3
+    namespace: envoy-gateway
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: All
+      name: http
+      port: 80
+      protocol: HTTP
+  status:
+    listeners:
+    - attachedRoutes: 1
+      conditions:
+      - lastTransitionTime: null
+        message: Sending translated listener configuration to the data plane
+        reason: Programmed
+        status: "True"
+        type: Programmed
+      - lastTransitionTime: null
+        message: Listener has been successfully translated
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Listener references have been resolved
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      name: http
+      supportedKinds:
+      - group: gateway.networking.k8s.io
+        kind: HTTPRoute
+      - group: gateway.networking.k8s.io
+        kind: GRPCRoute
 grpcRoutes:
 - apiVersion: gateway.networking.k8s.io/v1alpha2
   kind: GRPCRoute
@@ -196,6 +274,38 @@ grpcRoutes:
         name: gateway-1
         namespace: envoy-gateway
         sectionName: http
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: GRPCRoute
+  metadata:
+    name: grpcroute-2
+    namespace: default
+  spec:
+    parentRefs:
+    - name: gateway-3
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-3
+        namespace: envoy-gateway
+        sectionName: http
 httpRoutes:
 - apiVersion: gateway.networking.k8s.io/v1
   kind: HTTPRoute
@@ -273,6 +383,25 @@ infraIR:
           name: envoy-gateway-class
       name: envoy-gateway/gateway-2
       namespace: envoy-gateway-system
+  envoy-gateway/gateway-3:
+    proxy:
+      listeners:
+      - address: null
+        name: envoy-gateway/gateway-3/http
+        ports:
+        - containerPort: 10080
+          name: http-80
+          protocol: HTTP
+          servicePort: 80
+      metadata:
+        labels:
+          gateway.envoyproxy.io/owning-gateway-name: gateway-3
+          gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
+        ownerReference:
+          kind: GatewayClass
+          name: envoy-gateway-class
+      name: envoy-gateway/gateway-3
+      namespace: envoy-gateway-system
 xdsIR:
   envoy-gateway/gateway-1:
     accessLog:
@@ -463,3 +592,95 @@ xdsIR:
       ipFamily: IPv4
       path: /ready
       port: 19003
+  envoy-gateway/gateway-3:
+    accessLog:
+      json:
+      - path: /dev/stdout
+    globalResources:
+      envoyClientCertificate:
+        certificate: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUREVENDQWZXZ0F3SUJBZ0lVRUZNaFA5ZUo5WEFCV3NRNVptNmJSazJjTE5Rd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0ZqRVVNQklHQTFVRUF3d0xabTl2TG1KaGNpNWpiMjB3SGhjTk1qUXdNakk1TURrek1ERXdXaGNOTXpRdwpNakkyTURrek1ERXdXakFXTVJRd0VnWURWUVFEREF0bWIyOHVZbUZ5TG1OdmJUQ0NBU0l3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFKbEk2WXhFOVprQ1BzNnBDUXhickNtZWl4OVA1RGZ4OVJ1NUxENFQKSm1kVzdJS2R0UVYvd2ZMbXRzdTc2QithVGRDaldlMEJUZmVPT1JCYlIzY1BBRzZFbFFMaWNsUVVydW4zcStncwpKcEsrSTdjSStqNXc4STY4WEg1V1E3clZVdGJ3SHBxYncrY1ZuQnFJVU9MaUlhdGpJZjdLWDUxTTF1RjljZkVICkU0RG5jSDZyYnI1OS9SRlpCc2toeHM1T3p3Sklmb2hreXZGd2V1VHd4Sy9WcGpJKzdPYzQ4QUJDWHBOTzlEL3EKRWgrck9hdWpBTWNYZ0hRSVRrQ2lpVVRjVW82TFNIOXZMWlB0YXFmem9acTZuaE1xcFc2NUUxcEF3RjNqeVRUeAphNUk4SmNmU0Zqa2llWjIwTFVRTW43TThVNHhIamFvL2d2SDBDQWZkQjdSTFUyc0NBd0VBQWFOVE1GRXdIUVlEClZSME9CQllFRk9SQ0U4dS8xRERXN2loWnA3Y3g5dFNtUG02T01COEdBMVVkSXdRWU1CYUFGT1JDRTh1LzFERFcKN2loWnA3Y3g5dFNtUG02T01BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQgpBRnQ1M3pqc3FUYUg1YThFMmNodm1XQWdDcnhSSzhiVkxNeGl3TkdqYm1FUFJ6K3c2TngrazBBOEtFY0lEc0tjClNYY2k1OHU0b1didFZKQmx6YS9adWpIUjZQMUJuT3BsK2FveTc4NGJiZDRQMzl3VExvWGZNZmJCQ20xdmV2aDkKQUpLbncyWnRxcjRta2JMY3hFcWxxM3NCTEZBUzlzUUxuS05DZTJjR0xkVHAyYm9HK3FjZ3lRZ0NJTTZmOEVNdgpXUGlmQ01NR3V6Sy9HUkY0YlBPL1lGNDhld0R1M1VlaWgwWFhkVUFPRTlDdFVhOE5JaGMxVVBhT3pQcnRZVnFyClpPR2t2L0t1K0I3OGg4U0VzTzlYclFjdXdiT25KeDZLdFIrYWV5a3ZBcFhDUTNmWkMvYllLQUFSK1A4QUpvUVoKYndJVW1YaTRnajVtK2JLUGhlK2lyK0U9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
+        name: envoy-gateway-system/envoy
+        privateKey: '[redacted]'
+      proxyServiceCluster:
+        metadata:
+          name: envoy-envoy-gateway-gateway-3-32e857a6
+          namespace: envoy-gateway-system
+          sectionName: "8080"
+        name: envoy-gateway/gateway-3
+        settings:
+        - addressType: IP
+          endpoints:
+          - host: 7.6.5.4
+            port: 8080
+            zone: zone1
+          metadata:
+            name: envoy-envoy-gateway-gateway-3-32e857a6
+            namespace: envoy-gateway-system
+            sectionName: "8080"
+          name: envoy-gateway/gateway-3
+          protocol: TCP
+    http:
+    - address: 0.0.0.0
+      externalPort: 80
+      hostnames:
+      - '*'
+      isHTTP2: true
+      metadata:
+        kind: Gateway
+        name: gateway-3
+        namespace: envoy-gateway
+        sectionName: http
+      name: envoy-gateway/gateway-3/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - destination:
+          metadata:
+            kind: GRPCRoute
+            name: grpcroute-2
+            namespace: default
+          name: grpcroute/default/grpcroute-2/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            metadata:
+              name: service-1
+              namespace: default
+              sectionName: "8080"
+            name: grpcroute/default/grpcroute-2/rule/0/backend/0
+            protocol: GRPC
+            weight: 1
+        hostname: '*'
+        isHTTP2: true
+        metadata:
+          kind: GRPCRoute
+          name: grpcroute-2
+          namespace: default
+        name: grpcroute/default/grpcroute-2/rule/0/match/-1/*
+        traffic:
+          rateLimit:
+            global:
+              rules:
+              - headerMatches: []
+                limit:
+                  requests: 30
+                  unit: Hour
+                methodMatches:
+                - distinct: false
+                  exact: GET
+                  invert: true
+                  name: ""
+                name: default/policy-for-grpc-route/rule/0
+                pathMatch:
+                  distinct: false
+                  exact: /test
+                  name: ""
+    readyListener:
+      address: 0.0.0.0
+      ipFamily: IPv4
+      path: /ready
+      port: 19003
diff --git a/internal/xds/translator/testdata/in/ratelimit-config/method-match.yaml b/internal/xds/translator/testdata/in/ratelimit-config/method-match.yaml
new file mode 100644
index 000000000..c2055ed0d
--- /dev/null
+++ b/internal/xds/translator/testdata/in/ratelimit-config/method-match.yaml
@@ -0,0 +1,36 @@
+http:
+- name: "first-listener"
+  address: "0.0.0.0"
+  port: 10080
+  hostnames:
+  - "*"
+  path:
+    mergeSlashes: true
+    escapedSlashesAction: UnescapeAndRedirect
+  routes:
+  - name: "first-route"
+    traffic:
+      rateLimit:
+        global:
+          rules:
+          - name: "test-namespace/test-policy-1/rule/0"
+            methodMatches:
+            - exact: "GET"
+            - exact: "PUT"
+            limit:
+              requests: 5
+              unit: second
+          - name: "test-namespace/test-policy-1/rule/1"
+            methodMatches:
+            - exact: "POST"
+            limit:
+              requests: 50
+              unit: second
+    pathMatch:
+      exact: "foo/bar"
+    destination:
+      name: "first-route-dest"
+      settings:
+      - endpoints:
+        - host: "1.2.3.4"
+          port: 50000
diff --git a/internal/xds/translator/testdata/in/ratelimit-config/multiple-all-match.yaml b/internal/xds/translator/testdata/in/ratelimit-config/multiple-all-match.yaml
new file mode 100644
index 000000000..f69506bad
--- /dev/null
+++ b/internal/xds/translator/testdata/in/ratelimit-config/multiple-all-match.yaml
@@ -0,0 +1,45 @@
+http:
+- name: "first-listener"
+  address: "0.0.0.0"
+  port: 10080
+  hostnames:
+  - "*"
+  path:
+    mergeSlashes: true
+    escapedSlashesAction: UnescapeAndRedirect
+  routes:
+  - name: "first-route"
+    traffic:
+      rateLimit:
+        global:
+          rules:
+          - name: "test-namespace/test-policy-1/rule/0"
+            headerMatches:
+            - name: "x-user-id"
+              exact: "one"
+            - name: "x-user-id"
+              exact: "two"
+            - name: "x-org-id"
+              exact: "three"
+            cidrMatch:
+              cidr: 0.0.0.0/0
+              ip: 0.0.0.0
+              maskLen: 0
+              isIPv6: false
+              distinct: false
+            methodMatches:
+            - exact: "GET"
+            - exact: "POST"
+            pathMatch:
+              exact: "foo/bar/baz"
+            limit:
+              requests: 5
+              unit: second
+    pathMatch:
+      exact: "foo/bar"
+    destination:
+      name: "first-route-dest"
+      settings:
+      - endpoints:
+        - host: "1.2.3.4"
+          port: 50000
diff --git a/internal/xds/translator/testdata/in/ratelimit-config/path-match.yaml b/internal/xds/translator/testdata/in/ratelimit-config/path-match.yaml
new file mode 100644
index 000000000..252235ee4
--- /dev/null
+++ b/internal/xds/translator/testdata/in/ratelimit-config/path-match.yaml
@@ -0,0 +1,35 @@
+http:
+- name: "first-listener"
+  address: "0.0.0.0"
+  port: 10080
+  hostnames:
+  - "*"
+  path:
+    mergeSlashes: true
+    escapedSlashesAction: UnescapeAndRedirect
+  routes:
+  - name: "first-route"
+    traffic:
+      rateLimit:
+        global:
+          rules:
+          - name: "test-namespace/test-policy-1/rule/0"
+            pathMatch:
+              exact: "foo/bar"
+            limit:
+              requests: 5
+              unit: second
+          - name: "test-namespace/test-policy-1/rule/1"
+            pathMatch:
+              safeRegex: "foo/bar"
+            limit:
+              requests: 5
+              unit: second
+    pathMatch:
+      exact: "foo/bar"
+    destination:
+      name: "first-route-dest"
+      settings:
+      - endpoints:
+        - host: "1.2.3.4"
+          port: 50000
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/distinct-match.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/distinct-match.routes.yaml
new file mode 100644
index 000000000..1337d191a
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/distinct-match.routes.yaml
@@ -0,0 +1,12 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      RequestHeaders:
+        descriptor_key: rule-0-match-0
+        header_name: x-user-id
+routeName: first-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/distinct-remote-address-match.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/distinct-remote-address-match.routes.yaml
new file mode 100644
index 000000000..03b00685a
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/distinct-remote-address-match.routes.yaml
@@ -0,0 +1,14 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      MaskedRemoteAddress:
+        v4_prefix_mask_len:
+          value: 16
+  - ActionSpecifier:
+      RemoteAddress: {}
+routeName: first-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/empty-header-matches.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/empty-header-matches.routes.yaml
new file mode 100644
index 000000000..395311aff
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/empty-header-matches.routes.yaml
@@ -0,0 +1,12 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: rule-0-match--1
+        descriptor_value: rule-0-match--1
+routeName: first-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/global-shared-distinct-match.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/global-shared-distinct-match.routes.yaml
new file mode 100644
index 000000000..c0131d32a
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/global-shared-distinct-match.routes.yaml
@@ -0,0 +1,12 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: test-namespace/test-policy-1/rule/0
+        descriptor_value: test-namespace/test-policy-1/rule/0
+  - ActionSpecifier:
+      RequestHeaders:
+        descriptor_key: rule-0-match-0
+        header_name: x-user-id
+routeName: first-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/global-shared-multiple-shared-policies.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/global-shared-multiple-shared-policies.routes.yaml
new file mode 100644
index 000000000..da98addba
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/global-shared-multiple-shared-policies.routes.yaml
@@ -0,0 +1,83 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: test-namespace/test-policy-1/rule/0
+        descriptor_value: test-namespace/test-policy-1/rule/0
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: one
+          name: x-user-id
+routeName: first-route
+---
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: test-namespace/test-policy-1/rule/0
+        descriptor_value: test-namespace/test-policy-1/rule/0
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: one
+          name: x-user-id
+routeName: second-route
+---
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: test-namespace/test-policy-2/rule/0
+        descriptor_value: test-namespace/test-policy-2/rule/0
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: two
+          name: x-user-id
+routeName: third-route
+---
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: fourth-route
+        descriptor_value: fourth-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: three
+          name: x-user-id
+routeName: fourth-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/header-and-cidr-matches.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/header-and-cidr-matches.routes.yaml
new file mode 100644
index 000000000..1b3b97eee
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/header-and-cidr-matches.routes.yaml
@@ -0,0 +1,47 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: one
+          name: x-user-id
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-1
+        descriptor_value: rule-0-match-1
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: two
+          name: x-user-id
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-2
+        descriptor_value: rule-0-match-2
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: three
+          name: x-org-id
+  - ActionSpecifier:
+      MaskedRemoteAddress:
+        v4_prefix_mask_len: {}
+routeName: first-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/masked-remote-address-match.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/masked-remote-address-match.routes.yaml
new file mode 100644
index 000000000..ba1730475
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/masked-remote-address-match.routes.yaml
@@ -0,0 +1,12 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      MaskedRemoteAddress:
+        v4_prefix_mask_len:
+          value: 16
+routeName: first-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/method-match.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/method-match.routes.yaml
new file mode 100644
index 000000000..0707f7ad1
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/method-match.routes.yaml
@@ -0,0 +1,54 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-method
+        descriptor_value: rule-0-method
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: GET
+          name: :method
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-method
+        descriptor_value: rule-0-method
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: PUT
+          name: :method
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-1-method
+        descriptor_value: rule-1-method
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: POST
+          name: :method
+routeName: first-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/method-match.yaml b/internal/xds/translator/testdata/out/ratelimit-config/method-match.yaml
new file mode 100644
index 000000000..841f0e1c3
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/method-match.yaml
@@ -0,0 +1,31 @@
+name: first-listener
+domain: first-listener
+descriptors:
+  - key: first-route
+    value: first-route
+    rate_limit: null
+    descriptors:
+      - key: rule-0-method
+        value: rule-0-method
+        rate_limit:
+          requests_per_unit: 5
+          unit: SECOND
+          unlimited: false
+          name: ""
+          replaces: []
+        descriptors: []
+        shadow_mode: false
+        detailed_metric: false
+      - key: rule-1-method
+        value: rule-1-method
+        rate_limit:
+          requests_per_unit: 50
+          unit: SECOND
+          unlimited: false
+          name: ""
+          replaces: []
+        descriptors: []
+        shadow_mode: false
+        detailed_metric: false
+    shadow_mode: false
+    detailed_metric: false
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/month-year-rule.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/month-year-rule.routes.yaml
new file mode 100644
index 000000000..069bd860a
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/month-year-rule.routes.yaml
@@ -0,0 +1,41 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: one
+          name: x-user-id
+routeName: first-route
+---
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: second-route
+        descriptor_value: second-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: two
+          name: x-user-id
+routeName: second-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/multiple-all-match.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/multiple-all-match.routes.yaml
new file mode 100644
index 000000000..29c133f6e
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/multiple-all-match.routes.yaml
@@ -0,0 +1,139 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: one
+          name: x-user-id
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-1
+        descriptor_value: rule-0-match-1
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: two
+          name: x-user-id
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-2
+        descriptor_value: rule-0-match-2
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: three
+          name: x-org-id
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-method
+        descriptor_value: rule-0-method
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: GET
+          name: :method
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-path
+        descriptor_value: rule-0-path
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: foo/bar/baz
+          name: :path
+  - ActionSpecifier:
+      MaskedRemoteAddress:
+        v4_prefix_mask_len: {}
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: one
+          name: x-user-id
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-1
+        descriptor_value: rule-0-match-1
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: two
+          name: x-user-id
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-2
+        descriptor_value: rule-0-match-2
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: three
+          name: x-org-id
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-method
+        descriptor_value: rule-0-method
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: POST
+          name: :method
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-path
+        descriptor_value: rule-0-path
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: foo/bar/baz
+          name: :path
+  - ActionSpecifier:
+      MaskedRemoteAddress:
+        v4_prefix_mask_len: {}
+routeName: first-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/multiple-all-match.yaml b/internal/xds/translator/testdata/out/ratelimit-config/multiple-all-match.yaml
new file mode 100644
index 000000000..ed7e36ad7
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/multiple-all-match.yaml
@@ -0,0 +1,50 @@
+name: first-listener
+domain: first-listener
+descriptors:
+  - key: first-route
+    value: first-route
+    rate_limit: null
+    descriptors:
+      - key: rule-0-match-0
+        value: rule-0-match-0
+        rate_limit: null
+        descriptors:
+          - key: rule-0-match-1
+            value: rule-0-match-1
+            rate_limit: null
+            descriptors:
+              - key: rule-0-match-2
+                value: rule-0-match-2
+                rate_limit: null
+                descriptors:
+                  - key: rule-0-method
+                    value: rule-0-method
+                    rate_limit: null
+                    descriptors:
+                      - key: rule-0-path
+                        value: rule-0-path
+                        rate_limit: null
+                        descriptors:
+                          - key: masked_remote_address
+                            value: 0.0.0.0/0
+                            rate_limit:
+                              requests_per_unit: 5
+                              unit: SECOND
+                              unlimited: false
+                              name: ""
+                              replaces: []
+                            descriptors: []
+                            shadow_mode: false
+                            detailed_metric: false
+                        shadow_mode: false
+                        detailed_metric: false
+                    shadow_mode: false
+                    detailed_metric: false
+                shadow_mode: false
+                detailed_metric: false
+            shadow_mode: false
+            detailed_metric: false
+        shadow_mode: false
+        detailed_metric: false
+    shadow_mode: false
+    detailed_metric: false
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/multiple-domains.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/multiple-domains.routes.yaml
new file mode 100644
index 000000000..8ea694376
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/multiple-domains.routes.yaml
@@ -0,0 +1,41 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: test-namespace/test-policy-1/rule/0
+        descriptor_value: test-namespace/test-policy-1/rule/0
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: one
+          name: x-user-id
+routeName: first-route
+---
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: second-route
+        descriptor_value: second-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: one
+          name: x-user-id
+routeName: second-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/multiple-global-shared-distinct-match.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/multiple-global-shared-distinct-match.routes.yaml
new file mode 100644
index 000000000..219d19351
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/multiple-global-shared-distinct-match.routes.yaml
@@ -0,0 +1,30 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: test-namespace/test-policy-1/rule/0
+        descriptor_value: test-namespace/test-policy-1/rule/0
+  - ActionSpecifier:
+      RequestHeaders:
+        descriptor_key: rule-0-match-0
+        header_name: x-user-id
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: test-namespace/test-policy-1/rule/1
+        descriptor_value: test-namespace/test-policy-1/rule/1
+  - ActionSpecifier:
+      RequestHeaders:
+        descriptor_key: rule-1-match-0
+        header_name: x-user-id
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: test-namespace/test-policy-1/rule/2
+        descriptor_value: test-namespace/test-policy-1/rule/2
+  - ActionSpecifier:
+      RequestHeaders:
+        descriptor_key: rule-2-match-0
+        header_name: x-user-id
+routeName: first-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/multiple-listeners-distinct-match.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/multiple-listeners-distinct-match.routes.yaml
new file mode 100644
index 000000000..576b6000a
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/multiple-listeners-distinct-match.routes.yaml
@@ -0,0 +1,25 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      RequestHeaders:
+        descriptor_key: rule-0-match-0
+        header_name: x-user-id
+routeName: first-route
+---
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      RequestHeaders:
+        descriptor_key: rule-0-match-0
+        header_name: x-user-id
+routeName: first-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/multiple-listeners-same-shared-distinct-match.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/multiple-listeners-same-shared-distinct-match.routes.yaml
new file mode 100644
index 000000000..73e2bd199
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/multiple-listeners-same-shared-distinct-match.routes.yaml
@@ -0,0 +1,25 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: test-namespace/test-policy-1/rule/0
+        descriptor_value: test-namespace/test-policy-1/rule/0
+  - ActionSpecifier:
+      RequestHeaders:
+        descriptor_key: rule-0-match-0
+        header_name: x-user-id
+routeName: first-route
+---
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: test-namespace/test-policy-1/rule/0
+        descriptor_value: test-namespace/test-policy-1/rule/0
+  - ActionSpecifier:
+      RequestHeaders:
+        descriptor_key: rule-0-match-0
+        header_name: x-user-id
+routeName: first-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/multiple-listeners-shared-distinct-match.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/multiple-listeners-shared-distinct-match.routes.yaml
new file mode 100644
index 000000000..842cd0535
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/multiple-listeners-shared-distinct-match.routes.yaml
@@ -0,0 +1,25 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      RequestHeaders:
+        descriptor_key: rule-0-match-0
+        header_name: x-user-id
+routeName: first-route
+---
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: test-namespace/test-policy-2/rule/0
+        descriptor_value: test-namespace/test-policy-2/rule/0
+  - ActionSpecifier:
+      RequestHeaders:
+        descriptor_key: rule-0-match-0
+        header_name: x-user-id
+routeName: first-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/multiple-masked-remote-address-match-with-same-cidr.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/multiple-masked-remote-address-match-with-same-cidr.routes.yaml
new file mode 100644
index 000000000..9cc223dd9
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/multiple-masked-remote-address-match-with-same-cidr.routes.yaml
@@ -0,0 +1,25 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      MaskedRemoteAddress:
+        v4_prefix_mask_len:
+          value: 32
+routeName: first-route
+---
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: second-route
+        descriptor_value: second-route
+  - ActionSpecifier:
+      MaskedRemoteAddress:
+        v4_prefix_mask_len:
+          value: 32
+routeName: second-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/multiple-matches.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/multiple-matches.routes.yaml
new file mode 100644
index 000000000..f0d15a74d
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/multiple-matches.routes.yaml
@@ -0,0 +1,32 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: one
+          name: x-user-id
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-1
+        descriptor_value: rule-0-match-1
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: two
+          name: x-user-id
+routeName: first-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/multiple-routes.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/multiple-routes.routes.yaml
new file mode 100644
index 000000000..069bd860a
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/multiple-routes.routes.yaml
@@ -0,0 +1,41 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: one
+          name: x-user-id
+routeName: first-route
+---
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: second-route
+        descriptor_value: second-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: two
+          name: x-user-id
+routeName: second-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/multiple-rules.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/multiple-rules.routes.yaml
new file mode 100644
index 000000000..335e342c8
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/multiple-rules.routes.yaml
@@ -0,0 +1,37 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: one
+          name: x-user-id
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-1-match-0
+        descriptor_value: rule-1-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: two
+          name: x-user-id
+routeName: first-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/multiple-shared-and-unshared.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/multiple-shared-and-unshared.routes.yaml
new file mode 100644
index 000000000..229797a7b
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/multiple-shared-and-unshared.routes.yaml
@@ -0,0 +1,71 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: test-namespace/test-policy-1/rule/0
+        descriptor_value: test-namespace/test-policy-1/rule/0
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: one
+          name: x-user-id
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: two
+          name: x-user-id
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: test-namespace/test-policy-1/rule/2
+        descriptor_value: test-namespace/test-policy-1/rule/2
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-1-match-0
+        descriptor_value: rule-1-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: three
+          name: x-user-id
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-1-match-0
+        descriptor_value: rule-1-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: four
+          name: x-user-id
+routeName: first-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/multiple-shared-ratelimit-rules.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/multiple-shared-ratelimit-rules.routes.yaml
new file mode 100644
index 000000000..0ef9a5b42
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/multiple-shared-ratelimit-rules.routes.yaml
@@ -0,0 +1,54 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: test-namespace/test-policy-1/rule/0
+        descriptor_value: test-namespace/test-policy-1/rule/0
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: one
+          name: x-user-id
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: test-namespace/test-policy-1/rule/1
+        descriptor_value: test-namespace/test-policy-1/rule/1
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-1-match-0
+        descriptor_value: rule-1-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: two
+          name: x-user-id
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: three
+          name: x-user-id
+routeName: first-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/path-match.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/path-match.routes.yaml
new file mode 100644
index 000000000..2bb598f5e
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/path-match.routes.yaml
@@ -0,0 +1,39 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-path
+        descriptor_value: rule-0-path
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: foo/bar
+          name: :path
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-1-path
+        descriptor_value: rule-1-path
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                SafeRegex:
+                  EngineType: null
+                  regex: foo/bar
+          name: :path
+routeName: first-route
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/path-match.yaml b/internal/xds/translator/testdata/out/ratelimit-config/path-match.yaml
new file mode 100644
index 000000000..4cbb6d7b1
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/path-match.yaml
@@ -0,0 +1,31 @@
+name: first-listener
+domain: first-listener
+descriptors:
+  - key: first-route
+    value: first-route
+    rate_limit: null
+    descriptors:
+      - key: rule-0-path
+        value: rule-0-path
+        rate_limit:
+          requests_per_unit: 5
+          unit: SECOND
+          unlimited: false
+          name: ""
+          replaces: []
+        descriptors: []
+        shadow_mode: false
+        detailed_metric: false
+      - key: rule-1-path
+        value: rule-1-path
+        rate_limit:
+          requests_per_unit: 5
+          unit: SECOND
+          unlimited: false
+          name: ""
+          replaces: []
+        descriptors: []
+        shadow_mode: false
+        detailed_metric: false
+    shadow_mode: false
+    detailed_metric: false
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/value-match.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/value-match.routes.yaml
new file mode 100644
index 000000000..4686a1bd8
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/value-match.routes.yaml
@@ -0,0 +1,20 @@
+costSpecified: false
+rateLimits:
+- actions:
+  - ActionSpecifier:
+      GenericKey:
+        descriptor_key: first-route
+        descriptor_value: first-route
+  - ActionSpecifier:
+      HeaderValueMatch:
+        descriptor_key: rule-0-match-0
+        descriptor_value: rule-0-match-0
+        expect_match:
+          value: true
+        headers:
+        - HeaderMatchSpecifier:
+            StringMatch:
+              MatchPattern:
+                Exact: one
+          name: x-user-id
+routeName: first-route
diff --git a/internal/xds/translator/translator_test.go b/internal/xds/translator/translator_test.go
index a327119cd..0f1849377 100644
--- a/internal/xds/translator/translator_test.go
+++ b/internal/xds/translator/translator_test.go
@@ -7,6 +7,7 @@ package translator
 
 import (
 	"embed"
+	"encoding/json"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -15,6 +16,7 @@ import (
 	"testing"
 	"time"
 
+	routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
 	"github.com/envoyproxy/go-control-plane/pkg/cache/types"
 	resourcev3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3"
 	ratelimitv3 "github.com/envoyproxy/go-control-plane/ratelimit/config/ratelimit/v3"
@@ -50,6 +52,12 @@ type testFileConfig struct {
 	runtimeFlags              *egv1a1.RuntimeFlags
 }
 
+type rateLimitOutput struct {
+	RouteName     string               `json:"routeName" yaml:"routeName"`
+	RateLimits    []*routev3.RateLimit `json:"rateLimits" yaml:"rateLimits"`
+	CostSpecified bool                 `json:"costSpecified" yaml:"costSpecified"`
+}
+
 func TestTranslateXds(t *testing.T) {
 	// this is a hack to make sure EG render same output on macos and linux
 	defaultCertificateName = "/etc/ssl/certs/ca-certificates.crt"
@@ -247,6 +255,39 @@ func TestTranslateRateLimitConfig(t *testing.T) {
 	}
 }
 
+func TestBuildRouteRateLimits(t *testing.T) {
+	inputFiles, err := filepath.Glob(filepath.Join("testdata", "in", "ratelimit-config", "*.yaml"))
+	require.NoError(t, err)
+
+	for _, inputFile := range inputFiles {
+		inputFileName := testName(inputFile)
+		t.Run(inputFileName, func(t *testing.T) {
+			// Get listeners from the test data
+			listeners := requireXdsIRListenersFromInputTestData(t, inputFile)
+
+			var outputs []rateLimitOutput
+
+			// Process each route to get rate limit actions
+			for _, listener := range listeners {
+				for _, route := range listener.Routes {
+					rateLimits, costSpecified := buildRouteRateLimits(route)
+
+					output := rateLimitOutput{
+						RouteName:     route.Name,
+						RateLimits:    rateLimits,
+						CostSpecified: costSpecified,
+					}
+					outputs = append(outputs, output)
+				}
+			}
+			if test.OverrideTestData() {
+				require.NoError(t, file.Write(requireRouteRateLimitsToYAMLString(t, outputs), filepath.Join("testdata", "out", "ratelimit-config", inputFileName+".routes.yaml")))
+			}
+			require.Equal(t, requireTestDataOutFile(t, "ratelimit-config", inputFileName+".routes.yaml"), requireRouteRateLimitsToYAMLString(t, outputs))
+		})
+	}
+}
+
 // Simulate various extension server hooks and ensure that the translator returns the original resources
 // when configured to failOpen
 func TestTranslateXdsWithExtensionErrorsWhenFailOpen(t *testing.T) {
@@ -567,6 +608,24 @@ func requireRateLimitConfigsToYAMLString(t *testing.T, configs []*ratelimitv3.Ra
 	return result
 }
 
+func requireRouteRateLimitsToYAMLString(t *testing.T, rlOutputs []rateLimitOutput) string {
+	if len(rlOutputs) == 0 {
+		return ""
+	}
+
+	results := make([]string, 0, len(rlOutputs))
+	for _, output := range rlOutputs {
+		jsonBytes, err := json.Marshal(output)
+		require.NoError(t, err)
+		yamlBytes, err := yaml.JSONToYAML(jsonBytes)
+		require.NoError(t, err)
+
+		results = append(results, string(yamlBytes))
+	}
+
+	return strings.Join(results, "---\n")
+}
+
 func requireResourcesToYAMLString(t *testing.T, resources []types.Resource) string {
 	jsonBytes, err := utils.MarshalResourcesToJSON(resources)
 	require.NoError(t, err)
diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md
index c1ea50f53..1592fe841 100644
--- a/site/content/en/latest/api/extension_types.md
+++ b/site/content/en/latest/api/extension_types.md
@@ -1391,7 +1391,7 @@ _Appears in:_
 | ---   | ---  | ---      | ---     | ---         |
 | `rateLimitDeployment` | _[KubernetesDeploymentSpec](#kubernetesdeploymentspec)_ |  false  |  | RateLimitDeployment defines the desired state of the Envoy ratelimit deployment resource.<br />If unspecified, default settings for the managed Envoy ratelimit deployment resource<br />are applied. |
 | `rateLimitHpa` | _[KubernetesHorizontalPodAutoscalerSpec](#kuberneteshorizontalpodautoscalerspec)_ |  false  |  | RateLimitHpa defines the Horizontal Pod Autoscaler settings for Envoy ratelimit Deployment.<br />If the HPA is set, Replicas field from RateLimitDeployment will be ignored. |
-| `rateLimitPdb` | _[KubernetesPodDisruptionBudgetSpec](#kubernetespoddisruptionbudgetspec)_ |  false  |  | RateLimitPDB allows to control the pod disruption budget of rate limit service. |
+| `rateLimitPDB` | _[KubernetesPodDisruptionBudgetSpec](#kubernetespoddisruptionbudgetspec)_ |  false  |  | RateLimitPDB allows to control the pod disruption budget of rate limit service. |
 | `watch` | _[KubernetesWatchMode](#kuberneteswatchmode)_ |  false  |  | Watch holds configuration of which input resources should be watched and reconciled. |
 | `leaderElection` | _[LeaderElection](#leaderelection)_ |  false  |  | LeaderElection specifies the configuration for leader election.<br />If it's not set up, leader election will be active by default, using Kubernetes' standard settings. |
 | `shutdownManager` | _[ShutdownManager](#shutdownmanager)_ |  false  |  | ShutdownManager defines the configuration for the shutdown manager. |
@@ -3367,6 +3367,21 @@ _Appears in:_
 | `JSONMerge` | JSONMerge indicates a JSON merge patch type<br /> | 
 
 
+#### MethodMatch
+
+
+
+MethodMatch defines the matching criteria for the HTTP method of a request.
+
+_Appears in:_
+- [RateLimitSelectCondition](#ratelimitselectcondition)
+
+| Field | Type | Required | Default | Description |
+| ---   | ---  | ---      | ---     | ---         |
+| `value` | _[HTTPMethod](#httpmethod)_ |  true  |  | Value specifies the HTTP method. |
+| `invert` | _boolean_ |  false  | false | Invert specifies whether the value match result will be inverted. |
+
+
 #### MetricSinkType
 
 _Underlying type:_ _string_
@@ -3523,7 +3538,7 @@ _Appears in:_
 
 | Field | Type | Required | Default | Description |
 | ---   | ---  | ---      | ---     | ---         |
-| `methods` | _HTTPMethod array_ |  true  |  | Methods are the HTTP methods of the request.<br />If multiple methods are specified, all specified methods are allowed or denied, based on the action of the rule. |
+| `methods` | _[HTTPMethod](#httpmethod) array_ |  true  |  | Methods are the HTTP methods of the request.<br />If multiple methods are specified, all specified methods are allowed or denied, based on the action of the rule. |
 
 
 #### Origin
@@ -3605,6 +3620,22 @@ _Appears in:_
 | `UnescapeAndForward` | UnescapeAndForward unescapes %2F and %5C sequences and forwards the request.<br />Note: this option should not be enabled if intermediaries perform path based access<br />control as it may lead to path confusion vulnerabilities.<br /> | 
 
 
+#### PathMatch
+
+
+
+PathMatch defines the matching criteria for the HTTP path of a request.
+
+_Appears in:_
+- [RateLimitSelectCondition](#ratelimitselectcondition)
+
+| Field | Type | Required | Default | Description |
+| ---   | ---  | ---      | ---     | ---         |
+| `type` | _[PathMatchType](#pathmatchtype)_ |  false  | PathPrefix | Type specifies how to match against the value of the path. |
+| `value` | _string_ |  true  | / | Value specifies the HTTP path. |
+| `invert` | _boolean_ |  false  | false | Invert specifies whether the value match result will be inverted. |
+
+
 #### PathSettings
 
 
@@ -4261,14 +4292,17 @@ _Appears in:_
 RateLimitSelectCondition specifies the attributes within the traffic flow that can
 be used to select a subset of clients to be ratelimited.
 All the individual conditions must hold True for the overall condition to hold True.
+And, at least one of headers or methods or path or sourceCIDR condition must be specified.
 
 _Appears in:_
 - [RateLimitRule](#ratelimitrule)
 
 | Field | Type | Required | Default | Description |
 | ---   | ---  | ---      | ---     | ---         |
-| `headers` | _[HeaderMatch](#headermatch) array_ |  false  |  | Headers is a list of request headers to match. Multiple header values are ANDed together,<br />meaning, a request MUST match all the specified headers.<br />At least one of headers or sourceCIDR condition must be specified. |
-| `sourceCIDR` | _[SourceMatch](#sourcematch)_ |  false  |  | SourceCIDR is the client IP Address range to match on.<br />At least one of headers or sourceCIDR condition must be specified. |
+| `headers` | _[HeaderMatch](#headermatch) array_ |  false  |  | Headers is a list of request headers to match. Multiple header values are ANDed together,<br />meaning, a request MUST match all the specified headers. |
+| `methods` | _[MethodMatch](#methodmatch) array_ |  false  |  | Methods is a list of request methods to match. Multiple method values are ORed together,<br />meaning, a request can match any one of the specified methods. If not specified, it matches all methods. |
+| `path` | _[PathMatch](#pathmatch)_ |  false  |  | Path is the request path to match.<br />Support Exact, PathPrefix and RegularExpression match types. |
+| `sourceCIDR` | _[SourceMatch](#sourcematch)_ |  false  |  | SourceCIDR is the client IP Address range to match on. |
 
 
 #### RateLimitSpec
diff --git a/site/content/en/latest/tasks/traffic/global-rate-limit.md b/site/content/en/latest/tasks/traffic/global-rate-limit.md
index ee1a4950b..e4a295459 100644
--- a/site/content/en/latest/tasks/traffic/global-rate-limit.md
+++ b/site/content/en/latest/tasks/traffic/global-rate-limit.md
@@ -954,6 +954,373 @@ transfer-encoding: chunked
 
 ```
 
+## Rate Limit Based on Path
+
+Here is an example of a rate limit implemented by the application developer to limit requests based on the request path. In this case, we are limiting requests to `/api/users` to 3 requests/Hour.
+
+{{< tabpane text=true >}}
+{{% tab header="Apply from stdin" %}}
+
+```shell
+cat <<EOF | kubectl apply -f -
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy 
+metadata:
+  name: policy-httproute
+spec:
+  targetRefs:
+  - group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: http-ratelimit
+  rateLimit:
+    type: Global
+    global:
+      rules:
+      - clientSelectors:
+        - path:
+            type: Exact
+            value: /api/users
+        limit:
+          requests: 3
+          unit: Hour
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-ratelimit
+spec:
+  parentRefs:
+  - name: eg
+  hostnames:
+  - ratelimit.example 
+  rules:
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /api
+    backendRefs:
+    - group: ""
+      kind: Service
+      name: backend
+      port: 3000
+EOF
+```
+
+{{% /tab %}}
+{{% tab header="Apply from file" %}}
+Save and apply the following resource to your cluster:
+
+```yaml
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy 
+metadata:
+  name: policy-httproute
+spec:
+  targetRefs:
+  - group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: http-ratelimit
+  rateLimit:
+    type: Global
+    global:
+      rules:
+      - clientSelectors:
+        - path:
+            type: Exact
+            value: /api/users
+        limit:
+          requests: 3
+          unit: Hour
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-ratelimit
+spec:
+  parentRefs:
+  - name: eg
+  hostnames:
+  - ratelimit.example 
+  rules:
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /api
+    backendRefs:
+    - group: ""
+      kind: Service
+      name: backend
+      port: 3000
+```
+
+{{% /tab %}}
+{{< /tabpane >}}
+
+The path matching supports three types:
+- `Exact`: Matches the exact path (e.g., `/api/users`)
+- `PathPrefix`: Matches paths with a specific prefix (e.g., `/api/`)
+- `RegularExpression`: Matches paths using regex patterns (e.g., `/api/users/[0-9]+`)
+
+Let's query `/api/users` 4 times and `/api/products` 4 times. We should receive a `200` response for the first 3 requests to `/api/users`
+and a `429` status code for the 4th request. Requests to `/api/products` should not be rate limited:
+
+```shell
+# First, test the rate-limited path /api/users - 4 requests (4th should be rate limited)
+for i in {1..4}; do curl -I --header "Host: ratelimit.example" http://${GATEWAY_HOST}/api/users ; sleep 1; done
+
+# Now test a different path that should not be rate limited
+for i in {1..4}; do curl -I --header "Host: ratelimit.example" http://${GATEWAY_HOST}/api/products ; sleep 1; done
+```
+
+```console
+# Requests to /api/users (rate limited after 3 requests)
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:31 GMT
+content-length: 460
+x-envoy-upstream-service-time: 4
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:32 GMT
+content-length: 460
+x-envoy-upstream-service-time: 2
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:33 GMT
+content-length: 460
+x-envoy-upstream-service-time: 0
+server: envoy
+
+HTTP/1.1 429 Too Many Requests
+x-envoy-ratelimited: true
+date: Wed, 08 Feb 2023 02:33:34 GMT
+server: envoy
+transfer-encoding: chunked
+
+# Requests to /api/products (not rate limited)
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:35 GMT
+content-length: 460
+x-envoy-upstream-service-time: 4
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:36 GMT
+content-length: 460
+x-envoy-upstream-service-time: 2
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:37 GMT
+content-length: 460
+x-envoy-upstream-service-time: 0
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:38 GMT
+content-length: 460
+x-envoy-upstream-service-time: 0
+server: envoy
+```
+
+You can see that requests to `/api/users` are rate limited after the 3rd request (returning 429), while all requests to `/api/products` succeed without any rate limit.
+
+## Rate Limit Based on HTTP Method
+
+Here is an example of a rate limit implemented by the application developer to limit requests based on the HTTP method. In this case, we are limiting POST requests to 3 requests/Hour.
+
+{{< tabpane text=true >}}
+{{% tab header="Apply from stdin" %}}
+
+```shell
+cat <<EOF | kubectl apply -f -
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy 
+metadata:
+  name: policy-httproute
+spec:
+  targetRefs:
+  - group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: http-ratelimit
+  rateLimit:
+    type: Global
+    global:
+      rules:
+      - clientSelectors:
+        - methods:
+          - value: POST
+        limit:
+          requests: 3
+          unit: Hour
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-ratelimit
+spec:
+  parentRefs:
+  - name: eg
+  hostnames:
+  - ratelimit.example 
+  rules:
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /
+    backendRefs:
+    - group: ""
+      kind: Service
+      name: backend
+      port: 3000
+EOF
+```
+
+{{% /tab %}}
+{{% tab header="Apply from file" %}}
+Save and apply the following resource to your cluster:
+
+```yaml
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy 
+metadata:
+  name: policy-httproute
+spec:
+  targetRefs:
+  - group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: http-ratelimit
+  rateLimit:
+    type: Global
+    global:
+      rules:
+      - clientSelectors:
+        - methods:
+          - value: POST
+        limit:
+          requests: 3
+          unit: Hour
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-ratelimit
+spec:
+  parentRefs:
+  - name: eg
+  hostnames:
+  - ratelimit.example 
+  rules:
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /
+    backendRefs:
+    - group: ""
+      kind: Service
+      name: backend
+      port: 3000
+```
+
+{{% /tab %}}
+{{< /tabpane >}}
+
+Let's query the same path with POST and GET methods. We should receive a `200` response for the first 3 POST requests
+and a `429` status code for the 4th POST request. GET requests should not be rate limited:
+
+```shell
+# Send 4 POST requests - the 4th one should be rate limited
+for i in {1..4}; do curl -X POST -I --header "Host: ratelimit.example" http://${GATEWAY_HOST}/api/data ; sleep 1; done
+
+# GET requests should not be rate limited
+for i in {1..4}; do curl -X GET -I --header "Host: ratelimit.example" http://${GATEWAY_HOST}/api/data ; sleep 1; done
+```
+
+```console
+# POST requests (rate limited after 3 requests)
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:31 GMT
+content-length: 460
+x-envoy-upstream-service-time: 4
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:32 GMT
+content-length: 460
+x-envoy-upstream-service-time: 2
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:33 GMT
+content-length: 460
+x-envoy-upstream-service-time: 0
+server: envoy
+
+HTTP/1.1 429 Too Many Requests
+x-envoy-ratelimited: true
+date: Wed, 08 Feb 2023 02:33:34 GMT
+server: envoy
+transfer-encoding: chunked
+
+# GET requests (not rate limited)
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:35 GMT
+content-length: 460
+x-envoy-upstream-service-time: 4
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:36 GMT
+content-length: 460
+x-envoy-upstream-service-time: 2
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:37 GMT
+content-length: 460
+x-envoy-upstream-service-time: 0
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:38 GMT
+content-length: 460
+x-envoy-upstream-service-time: 0
+server: envoy
+```
+
+You can see that POST requests are rate limited after the 3rd request (returning 429), while GET requests to the same path are not rate limited.
+
 ## Rate Limit Jwt Claims
 
 Here is an example of a rate limit implemented by the application developer to limit distinct users who can be differentiated based on the value of the Jwt claims carried.
diff --git a/site/content/en/latest/tasks/traffic/local-rate-limit.md b/site/content/en/latest/tasks/traffic/local-rate-limit.md
index 05caf0725..c5492b823 100644
--- a/site/content/en/latest/tasks/traffic/local-rate-limit.md
+++ b/site/content/en/latest/tasks/traffic/local-rate-limit.md
@@ -620,6 +620,409 @@ transfer-encoding: chunked
 **Note:** Local rate limiting does not support `distinct` matching. If you want to rate limit based on distinct values, 
 you should use [Global Rate Limiting][]. 
 
+## Rate Limit Based on Path
+
+This example shows you how to rate limit requests based on the request path. In this case, we want to limit requests to `/api/users` to 3 requests/Hour.
+
+{{< tabpane text=true >}}
+{{% tab header="Apply from stdin" %}}
+
+```shell
+cat <<EOF | kubectl apply -f -
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy 
+metadata:
+  name: policy-httproute
+spec:
+  targetRefs:
+  - group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: http-ratelimit
+  rateLimit:
+    type: Local
+    local:
+      rules:
+      - clientSelectors:
+        - path:
+            type: Exact
+            value: /api/users
+        limit:
+          requests: 3
+          unit: Hour
+EOF
+```
+
+{{% /tab %}}
+{{% tab header="Apply from file" %}}
+Save and apply the following resource to your cluster:
+
+```yaml
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy 
+metadata:
+  name: policy-httproute
+spec:
+  targetRefs:
+  - group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: http-ratelimit
+  rateLimit:
+    type: Local
+    local:
+      rules:
+      - clientSelectors:
+        - path:
+            type: Exact
+            value: /api/users
+        limit:
+          requests: 3
+          unit: Hour
+```
+
+{{% /tab %}}
+{{< /tabpane >}}
+
+The path matching supports three types:
+- `Exact`: Matches the exact path (e.g., `/api/users`)
+- `PathPrefix`: Matches paths with a specific prefix (e.g., `/api/`)
+- `RegularExpression`: Matches paths using regex patterns (e.g., `/api/users/[0-9]+`)
+
+### HTTPRoute
+
+{{< tabpane text=true >}}
+{{% tab header="Apply from stdin" %}}
+
+```shell
+cat <<EOF | kubectl apply -f -
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-ratelimit
+spec:
+  parentRefs:
+  - name: eg
+  hostnames:
+  - ratelimit.example 
+  rules:
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /api
+    backendRefs:
+    - group: ""
+      kind: Service
+      name: backend
+      port: 3000
+EOF
+```
+
+{{% /tab %}}
+{{% tab header="Apply from file" %}}
+Save and apply the following resource to your cluster:
+
+```yaml
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-ratelimit
+spec:
+  parentRefs:
+  - name: eg
+  hostnames:
+  - ratelimit.example 
+  rules:
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /api
+    backendRefs:
+    - group: ""
+      kind: Service
+      name: backend
+      port: 3000
+```
+
+{{% /tab %}}
+{{< /tabpane >}}
+
+Let's test the path-based rate limit by querying `/api/users` and `/api/products`. The rate limit of 3 requests/Hour is only applied to `/api/users`:
+
+```shell
+# First, test the rate-limited path /api/users - 4 requests (4th should be rate limited)
+for i in {1..4}; do curl -I --header "Host: ratelimit.example" http://${GATEWAY_HOST}/api/users ; sleep 1; done
+
+# Now test a different path that should not be rate limited
+for i in {1..4}; do curl -I --header "Host: ratelimit.example" http://${GATEWAY_HOST}/api/products ; sleep 1; done
+```
+
+```console
+# Requests to /api/users (rate limited after 3 requests)
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:31 GMT
+content-length: 460
+x-envoy-upstream-service-time: 4
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:32 GMT
+content-length: 460
+x-envoy-upstream-service-time: 2
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:33 GMT
+content-length: 460
+x-envoy-upstream-service-time: 0
+server: envoy
+
+HTTP/1.1 429 Too Many Requests
+x-envoy-ratelimited: true
+date: Wed, 08 Feb 2023 02:33:34 GMT
+server: envoy
+transfer-encoding: chunked
+
+# Requests to /api/products (not rate limited)
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:35 GMT
+content-length: 460
+x-envoy-upstream-service-time: 4
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:36 GMT
+content-length: 460
+x-envoy-upstream-service-time: 2
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:37 GMT
+content-length: 460
+x-envoy-upstream-service-time: 0
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:38 GMT
+content-length: 460
+x-envoy-upstream-service-time: 0
+server: envoy
+```
+
+As you can see, requests to `/api/users` are rate limited after the 3rd request (returning 429), while all requests to `/api/products` succeed without any rate limit.
+
+## Rate Limit Based on HTTP Method
+
+This example shows you how to rate limit requests based on the HTTP method. In this case, we want to limit POST requests to 3 requests/Hour.
+
+{{< tabpane text=true >}}
+{{% tab header="Apply from stdin" %}}
+
+```shell
+cat <<EOF | kubectl apply -f -
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy 
+metadata:
+  name: policy-httproute
+spec:
+  targetRefs:
+  - group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: http-ratelimit
+  rateLimit:
+    type: Local
+    local:
+      rules:
+      - clientSelectors:
+        - methods:
+          - value: POST
+        limit:
+          requests: 3
+          unit: Hour
+EOF
+```
+
+{{% /tab %}}
+{{% tab header="Apply from file" %}}
+Save and apply the following resource to your cluster:
+
+```yaml
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy 
+metadata:
+  name: policy-httproute
+spec:
+  targetRefs:
+  - group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: http-ratelimit
+  rateLimit:
+    type: Local
+    local:
+      rules:
+      - clientSelectors:
+        - methods:
+          - value: POST
+        limit:
+          requests: 3
+          unit: Hour
+```
+
+{{% /tab %}}
+{{< /tabpane >}}
+
+### HTTPRoute
+
+{{< tabpane text=true >}}
+{{% tab header="Apply from stdin" %}}
+
+```shell
+cat <<EOF | kubectl apply -f -
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-ratelimit
+spec:
+  parentRefs:
+  - name: eg
+  hostnames:
+  - ratelimit.example 
+  rules:
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /
+    backendRefs:
+    - group: ""
+      kind: Service
+      name: backend
+      port: 3000
+EOF
+```
+
+{{% /tab %}}
+{{% tab header="Apply from file" %}}
+Save and apply the following resource to your cluster:
+
+```yaml
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-ratelimit
+spec:
+  parentRefs:
+  - name: eg
+  hostnames:
+  - ratelimit.example 
+  rules:
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /
+    backendRefs:
+    - group: ""
+      kind: Service
+      name: backend
+      port: 3000
+```
+
+{{% /tab %}}
+{{< /tabpane >}}
+
+Let's test the method-based rate limit. POST requests are limited to 3 requests/Hour, while GET requests are not limited:
+
+```shell
+# Send 4 POST requests - the 4th one should be rate limited
+for i in {1..4}; do curl -X POST -I --header "Host: ratelimit.example" http://${GATEWAY_HOST}/api/data ; sleep 1; done
+
+# GET requests should not be rate limited
+for i in {1..4}; do curl -X GET -I --header "Host: ratelimit.example" http://${GATEWAY_HOST}/api/data ; sleep 1; done
+```
+
+```console
+# POST requests (rate limited after 3 requests)
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:31 GMT
+content-length: 460
+x-envoy-upstream-service-time: 4
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:32 GMT
+content-length: 460
+x-envoy-upstream-service-time: 2
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:33 GMT
+content-length: 460
+x-envoy-upstream-service-time: 0
+server: envoy
+
+HTTP/1.1 429 Too Many Requests
+x-envoy-ratelimited: true
+date: Wed, 08 Feb 2023 02:33:34 GMT
+server: envoy
+transfer-encoding: chunked
+
+# GET requests (not rate limited)
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:35 GMT
+content-length: 460
+x-envoy-upstream-service-time: 4
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:36 GMT
+content-length: 460
+x-envoy-upstream-service-time: 2
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:37 GMT
+content-length: 460
+x-envoy-upstream-service-time: 0
+server: envoy
+
+HTTP/1.1 200 OK
+content-type: application/json
+x-content-type-options: nosniff
+date: Wed, 08 Feb 2023 02:33:38 GMT
+content-length: 460
+x-envoy-upstream-service-time: 0
+server: envoy
+```
+
+As you can see, POST requests are rate limited after the 3rd request (returning 429), while GET requests to the same path are not rate limited.
+
 [Global Rate Limiting]: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/other_features/global_rate_limiting
 [Local rate limiting]: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/other_features/local_rate_limiting
 [BackendTrafficPolicy]: ../../../api/extension_types#backendtrafficpolicy
diff --git a/test/cel-validation/backendtrafficpolicy_test.go b/test/cel-validation/backendtrafficpolicy_test.go
index a6c5a0b4f..2c272785d 100644
--- a/test/cel-validation/backendtrafficpolicy_test.go
+++ b/test/cel-validation/backendtrafficpolicy_test.go
@@ -2008,6 +2008,38 @@ func TestBackendTrafficPolicyTarget(t *testing.T) {
 			},
 			wantErrors: []string{`response cost is not supported for Local Rate Limits`},
 		},
+		{
+			desc: "invalid RateLimitSelectCondition with no selectors",
+			mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+				btp.Spec = egv1a1.BackendTrafficPolicySpec{
+					PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+						TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{
+							LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{
+								Group: gwapiv1.Group("gateway.networking.k8s.io"),
+								Kind:  gwapiv1.Kind("Gateway"),
+								Name:  gwapiv1.ObjectName("eg"),
+							},
+						},
+					},
+					RateLimit: &egv1a1.RateLimitSpec{
+						Type: egv1a1.GlobalRateLimitType,
+						Global: &egv1a1.GlobalRateLimit{
+							Rules: []egv1a1.RateLimitRule{
+								{
+									ClientSelectors: []egv1a1.RateLimitSelectCondition{
+										{
+											// No headers, methods, path, or sourceCIDR specified
+										},
+									},
+									Limit: egv1a1.RateLimitValue{Requests: 10, Unit: "Minute"},
+								},
+							},
+						},
+					},
+				}
+			},
+			wantErrors: []string{"at least one of headers, methods, path or sourceCIDR must be specified"},
+		},
 		{
 			desc: "panicThreshold is set",
 			mutate: func(btp *egv1a1.BackendTrafficPolicy) {
diff --git a/test/e2e/testdata/local-ratelimit.yaml b/test/e2e/testdata/local-ratelimit.yaml
index 2840eabb2..dc1232564 100644
--- a/test/e2e/testdata/local-ratelimit.yaml
+++ b/test/e2e/testdata/local-ratelimit.yaml
@@ -150,3 +150,82 @@ spec:
         - path:
             type: Exact
             value: /ratelimit-invert-match
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy
+metadata:
+  name: ratelimit-path-match
+  namespace: gateway-conformance-infra
+spec:
+  targetRefs:
+    - group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: http-ratelimit-path-match
+  rateLimit:
+    type: Local
+    local:
+      rules:
+        - clientSelectors:
+            - path:
+                type: Exact
+                value: /ratelimit-path-match/foo
+          limit:
+            requests: 3
+            unit: Hour
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-ratelimit-path-match
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+    - name: same-namespace
+    - name: all-namespaces
+  rules:
+    - backendRefs:
+        - name: infra-backend-v1
+          port: 8080
+      matches:
+        - path:
+            type: PathPrefix
+            value: /ratelimit-path-match
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy
+metadata:
+  name: ratelimit-method-match
+  namespace: gateway-conformance-infra
+spec:
+  targetRefs:
+    - group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: http-ratelimit-method-match
+  rateLimit:
+    type: Local
+    local:
+      rules:
+        - clientSelectors:
+            - methods:
+                - value: "GET"
+          limit:
+            requests: 3
+            unit: Hour
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-ratelimit-method-match
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+    - name: same-namespace
+    - name: all-namespaces
+  rules:
+    - backendRefs:
+        - name: infra-backend-v1
+          port: 8080
+      matches:
+        - path:
+            type: PathPrefix
+            value: /ratelimit-method-match
diff --git a/test/e2e/testdata/ratelimit-method-match.yaml b/test/e2e/testdata/ratelimit-method-match.yaml
new file mode 100644
index 000000000..7c53c492a
--- /dev/null
+++ b/test/e2e/testdata/ratelimit-method-match.yaml
@@ -0,0 +1,38 @@
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy
+metadata:
+  name: ratelimit-method-match
+  namespace: gateway-conformance-infra
+spec:
+  targetRefs:
+    - group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: method-ratelimit
+  rateLimit:
+    type: Global
+    global:
+      rules:
+        - clientSelectors:
+            - methods:
+                - value: "POST"
+                - value: "DELETE"
+          limit:
+            requests: 3
+            unit: Hour
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: method-ratelimit
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+    - name: same-namespace
+  rules:
+    - matches:
+        - path:
+            type: PathPrefix
+            value: /get
+      backendRefs:
+        - name: infra-backend-v1
+          port: 8080
diff --git a/test/e2e/testdata/ratelimit-path-match.yaml b/test/e2e/testdata/ratelimit-path-match.yaml
new file mode 100644
index 000000000..38e874da9
--- /dev/null
+++ b/test/e2e/testdata/ratelimit-path-match.yaml
@@ -0,0 +1,38 @@
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy
+metadata:
+  name: ratelimit-path-match
+  namespace: gateway-conformance-infra
+spec:
+  targetRefs:
+    - group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: path-ratelimit
+  rateLimit:
+    type: Global
+    global:
+      rules:
+        - clientSelectors:
+            - path:
+                type: PathPrefix
+                value: "/get/specific-path"
+          limit:
+            requests: 3
+            unit: Hour
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: path-ratelimit
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+    - name: same-namespace
+  rules:
+    - matches:
+        - path:
+            type: PathPrefix
+            value: /get
+      backendRefs:
+        - name: infra-backend-v1
+          port: 8080
diff --git a/test/e2e/tests/local_ratelimit.go b/test/e2e/tests/local_ratelimit.go
index a7c06b414..2b9543b31 100644
--- a/test/e2e/tests/local_ratelimit.go
+++ b/test/e2e/tests/local_ratelimit.go
@@ -60,6 +60,14 @@ var LocalRateLimitTest = suite.ConformanceTest{
 			t.Run(fmt.Sprintf("HeaderInvertMatch-%s", caseSuffix), func(t *testing.T) {
 				runHeaderInvertMatchRateLimitTest(t, suite, disableHeader)
 			})
+
+			t.Run(fmt.Sprintf("PathMatch-%s", caseSuffix), func(t *testing.T) {
+				runPathMatchRateLimitTest(t, suite, disableHeader)
+			})
+
+			t.Run(fmt.Sprintf("MethodMatch-%s", caseSuffix), func(t *testing.T) {
+				runMethodMatchRateLimitTest(t, suite, disableHeader)
+			})
 		}
 	},
 }
@@ -361,3 +369,138 @@ func runHeaderInvertMatchRateLimitTest(t *testing.T, suite *suite.ConformanceTes
 	}
 	http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, testOrgResponse)
 }
+
+func runPathMatchRateLimitTest(t *testing.T, suite *suite.ConformanceTestSuite, disableHeader bool) {
+	ns := "gateway-conformance-infra"
+	gwNN := gatewayNN(disableHeader)
+	gwAddr := gatewayAndHTTPRoutesMustBeAccepted(t, suite, gwNN)
+
+	ancestorRef := gwapiv1.ParentReference{
+		Group:     gatewayapi.GroupPtr(gwapiv1.GroupName),
+		Kind:      gatewayapi.KindPtr(resource.KindGateway),
+		Namespace: gatewayapi.NamespacePtr(gwNN.Namespace),
+		Name:      gwapiv1.ObjectName(gwNN.Name),
+	}
+	BackendTrafficPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "ratelimit-path-match", Namespace: ns}, suite.ControllerName, ancestorRef)
+
+	okResponse := http.ExpectedResponse{
+		Request: http.Request{
+			Path: "/ratelimit-path-match/foo",
+		},
+		Response: http.Response{
+			StatusCode: 200,
+		},
+		Namespace: ns,
+	}
+	if !disableHeader {
+		okResponse.Response.Headers = map[string]string{
+			RatelimitLimitHeaderName:     "3",
+			RatelimitRemainingHeaderName: "1",
+			RatelimitResetHeaderName:     "0",
+		}
+	} else {
+		okResponse.Response.AbsentHeaders = allRateLimitHeaders
+	}
+	// keep sending requests till get 200 first, that will cost one 200
+	http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, okResponse)
+
+	limitResponse := http.ExpectedResponse{
+		Request: http.Request{
+			Path: "/ratelimit-path-match/foo",
+		},
+		Response: http.Response{
+			StatusCode: 429,
+		},
+		Namespace: ns,
+	}
+	if !disableHeader {
+		limitResponse.Response.Headers = map[string]string{
+			RatelimitLimitHeaderName:     "3",
+			RatelimitRemainingHeaderName: "0", // at the end the remaining should be 0
+		}
+	} else {
+		limitResponse.Response.AbsentHeaders = allRateLimitHeaders
+	}
+	// this request should be limited at the end
+	http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, limitResponse)
+
+	okResponse = http.ExpectedResponse{
+		Request: http.Request{
+			Path: "/ratelimit-path-match/bar",
+		},
+		Response: http.Response{
+			StatusCode: 200,
+		},
+		Namespace: ns,
+	}
+	// this request should not be limited
+	http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, okResponse)
+}
+
+func runMethodMatchRateLimitTest(t *testing.T, suite *suite.ConformanceTestSuite, disableHeader bool) {
+	ns := "gateway-conformance-infra"
+	gwNN := gatewayNN(disableHeader)
+	gwAddr := gatewayAndHTTPRoutesMustBeAccepted(t, suite, gwNN)
+
+	ancestorRef := gwapiv1.ParentReference{
+		Group:     gatewayapi.GroupPtr(gwapiv1.GroupName),
+		Kind:      gatewayapi.KindPtr(resource.KindGateway),
+		Namespace: gatewayapi.NamespacePtr(gwNN.Namespace),
+		Name:      gwapiv1.ObjectName(gwNN.Name),
+	}
+	BackendTrafficPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "ratelimit-method-match", Namespace: ns}, suite.ControllerName, ancestorRef)
+
+	okResponse := http.ExpectedResponse{
+		Request: http.Request{
+			Path: "/ratelimit-method-match",
+		},
+		Response: http.Response{
+			StatusCode: 200,
+		},
+		Namespace: ns,
+	}
+	if !disableHeader {
+		okResponse.Response.Headers = map[string]string{
+			RatelimitLimitHeaderName:     "3",
+			RatelimitRemainingHeaderName: "1",
+			RatelimitResetHeaderName:     "0",
+		}
+	} else {
+		okResponse.Response.AbsentHeaders = allRateLimitHeaders
+	}
+	// keep sending requests till get 200 first, that will cost one 200
+	http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, okResponse)
+
+	limitResponse := http.ExpectedResponse{
+		Request: http.Request{
+			Path: "/ratelimit-method-match",
+		},
+		Response: http.Response{
+			StatusCode: 429,
+		},
+		Namespace: ns,
+	}
+	if !disableHeader {
+		limitResponse.Response.Headers = map[string]string{
+			RatelimitLimitHeaderName:     "3",
+			RatelimitRemainingHeaderName: "0", // at the end the remaining should be 0
+		}
+	} else {
+		limitResponse.Response.AbsentHeaders = allRateLimitHeaders
+	}
+	// this request should be limited at the end
+	http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, limitResponse)
+
+	okResponse = http.ExpectedResponse{
+		Request: http.Request{
+			Path:   "/ratelimit-method-match",
+			Method: "POST",
+		},
+		Response: http.Response{
+			StatusCode: 200,
+		},
+		Namespace: ns,
+	}
+	// this request should not be limited
+	http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, okResponse)
+}
diff --git a/test/e2e/tests/ratelimit.go b/test/e2e/tests/ratelimit.go
index 06b80e405..8d5b1cefa 100644
--- a/test/e2e/tests/ratelimit.go
+++ b/test/e2e/tests/ratelimit.go
@@ -33,6 +33,8 @@ func init() {
 	ConformanceTests = append(ConformanceTests,
 		RateLimitCIDRMatchTest,
 		RateLimitHeaderMatchTest,
+		RateLimitMethodMatchTest,
+		RateLimitPathMatchTest,
 		GlobalRateLimitHeaderInvertMatchTest,
 		RateLimitHeadersDisabled,
 		RateLimitBasedJwtClaimsTest,
@@ -111,6 +113,179 @@ var RateLimitCIDRMatchTest = suite.ConformanceTest{
 	},
 }
 
+var RateLimitMethodMatchTest = suite.ConformanceTest{
+	ShortName:   "RateLimitMethodMatch",
+	Description: "Limit all requests that match method",
+	Manifests:   []string{"testdata/ratelimit-method-match.yaml"},
+	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
+		ns := "gateway-conformance-infra"
+		routeNN := types.NamespacedName{Name: "method-ratelimit", Namespace: ns}
+		gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
+		gwAddr := kubernetes.GatewayAndRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), &gwapiv1.HTTPRoute{}, false, routeNN)
+
+		t.Run("matched method can got limited", func(t *testing.T) {
+			ratelimitHeader := make(map[string]string)
+
+			expectOkResp := http.ExpectedResponse{
+				Request: http.Request{
+					Path:   "/get",
+					Method: "POST",
+				},
+				Response: http.Response{
+					StatusCode: 200,
+					Headers:    ratelimitHeader,
+				},
+				Namespace: ns,
+			}
+			expectOkResp.Response.Headers["X-Ratelimit-Limit"] = "3, 3;w=3600"
+			expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http")
+
+			expectLimitResp := http.ExpectedResponse{
+				Request: http.Request{
+					Path:   "/get",
+					Method: "POST",
+				},
+				Response: http.Response{
+					StatusCode: 429,
+				},
+				Namespace: ns,
+			}
+			expectLimitReq := http.MakeRequest(t, &expectLimitResp, gwAddr, "HTTP", "http")
+
+			expectLimitDeleteReq := http.ExpectedResponse{
+				Request: http.Request{
+					Path:   "/get",
+					Method: "DELETE",
+				},
+				Response: http.Response{
+					StatusCode: 429,
+				},
+				Namespace: ns,
+			}
+			expectLimitDeleteResp := http.MakeRequest(t, &expectLimitDeleteReq, gwAddr, "HTTP", "http")
+
+			// should just send exactly 4 requests, and expect 429
+
+			// keep sending requests till get 200 first, that will cost one 200
+			http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp)
+
+			// fire the rest of the requests
+			if err := GotExactExpectedResponse(t, 2, suite.RoundTripper, expectOkReq, expectOkResp); err != nil {
+				t.Errorf("failed to get expected response for the first three requests: %v", err)
+			}
+			if err := GotExactExpectedResponse(t, 1, suite.RoundTripper, expectLimitReq, expectLimitResp); err != nil {
+				t.Errorf("failed to get expected response for the last (fourth) request: %v", err)
+			}
+			// Since POST method already consumed the rate limit quota,
+			// DELETE request should be immediately rate limited (both methods share the same counter).
+			if err := GotExactExpectedResponse(t, 1, suite.RoundTripper, expectLimitDeleteResp, expectLimitDeleteReq); err != nil {
+				t.Errorf("failed to get expected response for the limited delete request: %v", err)
+			}
+		})
+
+		t.Run("not matched method cannot got limited", func(t *testing.T) {
+			// it does not require any rate limit header, since this request never be rate limited.
+			expectOkResp := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/get",
+				},
+				Response: http.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+			expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http")
+
+			// send exactly 4 requests, and still expect 200
+
+			// keep sending requests till get 200 first, that will cost one 200
+			http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp)
+
+			// fire the rest of the requests
+			if err := GotExactExpectedResponse(t, 3, suite.RoundTripper, expectOkReq, expectOkResp); err != nil {
+				t.Errorf("failed to get expected responses for the request: %v", err)
+			}
+		})
+	},
+}
+
+var RateLimitPathMatchTest = suite.ConformanceTest{
+	ShortName:   "RateLimitPathMatch",
+	Description: "Limit all requests that match path",
+	Manifests:   []string{"testdata/ratelimit-path-match.yaml"},
+	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
+		ns := "gateway-conformance-infra"
+		routeNN := types.NamespacedName{Name: "path-ratelimit", Namespace: ns}
+		gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
+		gwAddr := kubernetes.GatewayAndRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), &gwapiv1.HTTPRoute{}, false, routeNN)
+
+		t.Run("matched path can got limited", func(t *testing.T) {
+			ratelimitHeader := make(map[string]string)
+
+			expectOkResp := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/get/specific-path",
+				},
+				Response: http.Response{
+					StatusCode: 200,
+					Headers:    ratelimitHeader,
+				},
+				Namespace: ns,
+			}
+			expectOkResp.Response.Headers["X-Ratelimit-Limit"] = "3, 3;w=3600"
+			expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http")
+
+			expectLimitResp := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/get/specific-path",
+				},
+				Response: http.Response{
+					StatusCode: 429,
+				},
+				Namespace: ns,
+			}
+			expectLimitReq := http.MakeRequest(t, &expectLimitResp, gwAddr, "HTTP", "http")
+
+			// should just send exactly 4 requests, and expect 429
+
+			// keep sending requests till get 200 first, that will cost one 200
+			http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp)
+
+			// fire the rest of the requests
+			if err := GotExactExpectedResponse(t, 2, suite.RoundTripper, expectOkReq, expectOkResp); err != nil {
+				t.Errorf("failed to get expected response for the first three requests: %v", err)
+			}
+			if err := GotExactExpectedResponse(t, 1, suite.RoundTripper, expectLimitReq, expectLimitResp); err != nil {
+				t.Errorf("failed to get expected response for the last (fourth) request: %v", err)
+			}
+		})
+
+		t.Run("not matched path cannot got limited", func(t *testing.T) {
+			// it does not require any rate limit header, since this request never be rate limited.
+			expectOkResp := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/get/no-specific-path",
+				},
+				Response: http.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+			expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http")
+
+			// send exactly 4 requests, and still expect 200
+
+			// keep sending requests till get 200 first, that will cost one 200
+			http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp)
+
+			// fire the rest of the requests
+			if err := GotExactExpectedResponse(t, 3, suite.RoundTripper, expectOkReq, expectOkResp); err != nil {
+				t.Errorf("failed to get expected responses for the request: %v", err)
+			}
+		})
+	},
+}
+
 var RateLimitHeaderMatchTest = suite.ConformanceTest{
 	ShortName:   "RateLimitHeaderMatch",
 	Description: "Limit all requests that match headers",
diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml
index bf1b44abc..aaf27e53b 100644
--- a/test/helm/gateway-crds-helm/all.out.yaml
+++ b/test/helm/gateway-crds-helm/all.out.yaml
@@ -22081,12 +22081,12 @@ spec:
                                   RateLimitSelectCondition specifies the attributes within the traffic flow that can
                                   be used to select a subset of clients to be ratelimited.
                                   All the individual conditions must hold True for the overall condition to hold True.
+                                  And, at least one of headers or methods or path or sourceCIDR condition must be specified.
                                 properties:
                                   headers:
                                     description: |-
                                       Headers is a list of request headers to match. Multiple header values are ANDed together,
                                       meaning, a request MUST match all the specified headers.
-                                      At least one of headers or sourceCIDR condition must be specified.
                                     items:
                                       description: HeaderMatch defines the match attributes
                                         within the HTTP Headers of the request.
@@ -22127,10 +22127,66 @@ spec:
                                       type: object
                                     maxItems: 16
                                     type: array
-                                  sourceCIDR:
+                                  methods:
+                                    description: |-
+                                      Methods is a list of request methods to match. Multiple method values are ORed together,
+                                      meaning, a request can match any one of the specified methods. If not specified, it matches all methods.
+                                    items:
+                                      description: MethodMatch defines the matching
+                                        criteria for the HTTP method of a request.
+                                      properties:
+                                        invert:
+                                          default: false
+                                          description: Invert specifies whether the
+                                            value match result will be inverted.
+                                          type: boolean
+                                        value:
+                                          description: Value specifies the HTTP method.
+                                          enum:
+                                          - GET
+                                          - HEAD
+                                          - POST
+                                          - PUT
+                                          - DELETE
+                                          - CONNECT
+                                          - OPTIONS
+                                          - TRACE
+                                          - PATCH
+                                          type: string
+                                      required:
+                                      - value
+                                      type: object
+                                    type: array
+                                  path:
                                     description: |-
-                                      SourceCIDR is the client IP Address range to match on.
-                                      At least one of headers or sourceCIDR condition must be specified.
+                                      Path is the request path to match.
+                                      Support Exact, PathPrefix and RegularExpression match types.
+                                    properties:
+                                      invert:
+                                        default: false
+                                        description: Invert specifies whether the
+                                          value match result will be inverted.
+                                        type: boolean
+                                      type:
+                                        default: PathPrefix
+                                        description: Type specifies how to match against
+                                          the value of the path.
+                                        enum:
+                                        - Exact
+                                        - PathPrefix
+                                        - RegularExpression
+                                        type: string
+                                      value:
+                                        default: /
+                                        description: Value specifies the HTTP path.
+                                        maxLength: 1024
+                                        type: string
+                                    required:
+                                    - value
+                                    type: object
+                                  sourceCIDR:
+                                    description: SourceCIDR is the client IP Address
+                                      range to match on.
                                     properties:
                                       type:
                                         default: Exact
@@ -22150,6 +22206,11 @@ spec:
                                     - value
                                     type: object
                                 type: object
+                                x-kubernetes-validations:
+                                - message: at least one of headers, methods, path
+                                    or sourceCIDR must be specified
+                                  rule: has(self.headers) || has(self.methods) ||
+                                    has(self.path) || has(self.sourceCIDR)
                               maxItems: 8
                               type: array
                             cost:
@@ -22332,12 +22393,12 @@ spec:
                                   RateLimitSelectCondition specifies the attributes within the traffic flow that can
                                   be used to select a subset of clients to be ratelimited.
                                   All the individual conditions must hold True for the overall condition to hold True.
+                                  And, at least one of headers or methods or path or sourceCIDR condition must be specified.
                                 properties:
                                   headers:
                                     description: |-
                                       Headers is a list of request headers to match. Multiple header values are ANDed together,
                                       meaning, a request MUST match all the specified headers.
-                                      At least one of headers or sourceCIDR condition must be specified.
                                     items:
                                       description: HeaderMatch defines the match attributes
                                         within the HTTP Headers of the request.
@@ -22378,10 +22439,66 @@ spec:
                                       type: object
                                     maxItems: 16
                                     type: array
-                                  sourceCIDR:
+                                  methods:
+                                    description: |-
+                                      Methods is a list of request methods to match. Multiple method values are ORed together,
+                                      meaning, a request can match any one of the specified methods. If not specified, it matches all methods.
+                                    items:
+                                      description: MethodMatch defines the matching
+                                        criteria for the HTTP method of a request.
+                                      properties:
+                                        invert:
+                                          default: false
+                                          description: Invert specifies whether the
+                                            value match result will be inverted.
+                                          type: boolean
+                                        value:
+                                          description: Value specifies the HTTP method.
+                                          enum:
+                                          - GET
+                                          - HEAD
+                                          - POST
+                                          - PUT
+                                          - DELETE
+                                          - CONNECT
+                                          - OPTIONS
+                                          - TRACE
+                                          - PATCH
+                                          type: string
+                                      required:
+                                      - value
+                                      type: object
+                                    type: array
+                                  path:
                                     description: |-
-                                      SourceCIDR is the client IP Address range to match on.
-                                      At least one of headers or sourceCIDR condition must be specified.
+                                      Path is the request path to match.
+                                      Support Exact, PathPrefix and RegularExpression match types.
+                                    properties:
+                                      invert:
+                                        default: false
+                                        description: Invert specifies whether the
+                                          value match result will be inverted.
+                                        type: boolean
+                                      type:
+                                        default: PathPrefix
+                                        description: Type specifies how to match against
+                                          the value of the path.
+                                        enum:
+                                        - Exact
+                                        - PathPrefix
+                                        - RegularExpression
+                                        type: string
+                                      value:
+                                        default: /
+                                        description: Value specifies the HTTP path.
+                                        maxLength: 1024
+                                        type: string
+                                    required:
+                                    - value
+                                    type: object
+                                  sourceCIDR:
+                                    description: SourceCIDR is the client IP Address
+                                      range to match on.
                                     properties:
                                       type:
                                         default: Exact
@@ -22401,6 +22518,11 @@ spec:
                                     - value
                                     type: object
                                 type: object
+                                x-kubernetes-validations:
+                                - message: at least one of headers, methods, path
+                                    or sourceCIDR must be specified
+                                  rule: has(self.headers) || has(self.methods) ||
+                                    has(self.path) || has(self.sourceCIDR)
                               maxItems: 8
                               type: array
                             cost:
diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml
index 0c674faac..8833c22fa 100644
--- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml
+++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml
@@ -1425,12 +1425,12 @@ spec:
                                   RateLimitSelectCondition specifies the attributes within the traffic flow that can
                                   be used to select a subset of clients to be ratelimited.
                                   All the individual conditions must hold True for the overall condition to hold True.
+                                  And, at least one of headers or methods or path or sourceCIDR condition must be specified.
                                 properties:
                                   headers:
                                     description: |-
                                       Headers is a list of request headers to match. Multiple header values are ANDed together,
                                       meaning, a request MUST match all the specified headers.
-                                      At least one of headers or sourceCIDR condition must be specified.
                                     items:
                                       description: HeaderMatch defines the match attributes
                                         within the HTTP Headers of the request.
@@ -1471,10 +1471,66 @@ spec:
                                       type: object
                                     maxItems: 16
                                     type: array
-                                  sourceCIDR:
+                                  methods:
                                     description: |-
-                                      SourceCIDR is the client IP Address range to match on.
-                                      At least one of headers or sourceCIDR condition must be specified.
+                                      Methods is a list of request methods to match. Multiple method values are ORed together,
+                                      meaning, a request can match any one of the specified methods. If not specified, it matches all methods.
+                                    items:
+                                      description: MethodMatch defines the matching
+                                        criteria for the HTTP method of a request.
+                                      properties:
+                                        invert:
+                                          default: false
+                                          description: Invert specifies whether the
+                                            value match result will be inverted.
+                                          type: boolean
+                                        value:
+                                          description: Value specifies the HTTP method.
+                                          enum:
+                                          - GET
+                                          - HEAD
+                                          - POST
+                                          - PUT
+                                          - DELETE
+                                          - CONNECT
+                                          - OPTIONS
+                                          - TRACE
+                                          - PATCH
+                                          type: string
+                                      required:
+                                      - value
+                                      type: object
+                                    type: array
+                                  path:
+                                    description: |-
+                                      Path is the request path to match.
+                                      Support Exact, PathPrefix and RegularExpression match types.
+                                    properties:
+                                      invert:
+                                        default: false
+                                        description: Invert specifies whether the
+                                          value match result will be inverted.
+                                        type: boolean
+                                      type:
+                                        default: PathPrefix
+                                        description: Type specifies how to match against
+                                          the value of the path.
+                                        enum:
+                                        - Exact
+                                        - PathPrefix
+                                        - RegularExpression
+                                        type: string
+                                      value:
+                                        default: /
+                                        description: Value specifies the HTTP path.
+                                        maxLength: 1024
+                                        type: string
+                                    required:
+                                    - value
+                                    type: object
+                                  sourceCIDR:
+                                    description: SourceCIDR is the client IP Address
+                                      range to match on.
                                     properties:
                                       type:
                                         default: Exact
@@ -1494,6 +1550,11 @@ spec:
                                     - value
                                     type: object
                                 type: object
+                                x-kubernetes-validations:
+                                - message: at least one of headers, methods, path
+                                    or sourceCIDR must be specified
+                                  rule: has(self.headers) || has(self.methods) ||
+                                    has(self.path) || has(self.sourceCIDR)
                               maxItems: 8
                               type: array
                             cost:
@@ -1676,12 +1737,12 @@ spec:
                                   RateLimitSelectCondition specifies the attributes within the traffic flow that can
                                   be used to select a subset of clients to be ratelimited.
                                   All the individual conditions must hold True for the overall condition to hold True.
+                                  And, at least one of headers or methods or path or sourceCIDR condition must be specified.
                                 properties:
                                   headers:
                                     description: |-
                                       Headers is a list of request headers to match. Multiple header values are ANDed together,
                                       meaning, a request MUST match all the specified headers.
-                                      At least one of headers or sourceCIDR condition must be specified.
                                     items:
                                       description: HeaderMatch defines the match attributes
                                         within the HTTP Headers of the request.
@@ -1722,10 +1783,66 @@ spec:
                                       type: object
                                     maxItems: 16
                                     type: array
-                                  sourceCIDR:
+                                  methods:
                                     description: |-
-                                      SourceCIDR is the client IP Address range to match on.
-                                      At least one of headers or sourceCIDR condition must be specified.
+                                      Methods is a list of request methods to match. Multiple method values are ORed together,
+                                      meaning, a request can match any one of the specified methods. If not specified, it matches all methods.
+                                    items:
+                                      description: MethodMatch defines the matching
+                                        criteria for the HTTP method of a request.
+                                      properties:
+                                        invert:
+                                          default: false
+                                          description: Invert specifies whether the
+                                            value match result will be inverted.
+                                          type: boolean
+                                        value:
+                                          description: Value specifies the HTTP method.
+                                          enum:
+                                          - GET
+                                          - HEAD
+                                          - POST
+                                          - PUT
+                                          - DELETE
+                                          - CONNECT
+                                          - OPTIONS
+                                          - TRACE
+                                          - PATCH
+                                          type: string
+                                      required:
+                                      - value
+                                      type: object
+                                    type: array
+                                  path:
+                                    description: |-
+                                      Path is the request path to match.
+                                      Support Exact, PathPrefix and RegularExpression match types.
+                                    properties:
+                                      invert:
+                                        default: false
+                                        description: Invert specifies whether the
+                                          value match result will be inverted.
+                                        type: boolean
+                                      type:
+                                        default: PathPrefix
+                                        description: Type specifies how to match against
+                                          the value of the path.
+                                        enum:
+                                        - Exact
+                                        - PathPrefix
+                                        - RegularExpression
+                                        type: string
+                                      value:
+                                        default: /
+                                        description: Value specifies the HTTP path.
+                                        maxLength: 1024
+                                        type: string
+                                    required:
+                                    - value
+                                    type: object
+                                  sourceCIDR:
+                                    description: SourceCIDR is the client IP Address
+                                      range to match on.
                                     properties:
                                       type:
                                         default: Exact
@@ -1745,6 +1862,11 @@ spec:
                                     - value
                                     type: object
                                 type: object
+                                x-kubernetes-validations:
+                                - message: at least one of headers, methods, path
+                                    or sourceCIDR must be specified
+                                  rule: has(self.headers) || has(self.methods) ||
+                                    has(self.path) || has(self.sourceCIDR)
                               maxItems: 8
                               type: array
                             cost: