Repository: kubernetes/ingress-nginx
Pull request #10081 (2023-06-13 15:10:26)

Revert "Remove fastcgi feature"
Reverts kubernetes/ingress-nginx#9864

After the #9864 was merged, we received feedback from users. After discussion, we decided to roll it back.

ref: https://github.com/kubernetes/ingress-nginx/pull/9864#issuecomment-1589484819

k8s-ci-robot: This issue is currently awaiting triage.

If Ingress contributors determines this is a relevant issue, they will accept it by applying the `triage/accepted` label and provide further guidance.

The `triage/accepted` label can be added by org members by writing `/triage accepted` in a comment.


<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/ingress-nginx/pull/10081#" title="Author self-approved">tao12345666333</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fingress-nginx).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes/ingress-nginx/blob/main/OWNERS)~~ [tao12345666333]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
tao12345666333: /hold
rikatz: /lgtm
Let me just wait my annotatioin validation stuff to pass, then I will rebase over this one again once merged (aaaaaah) and add the validation on this feature
adoy: @tao12345666333 You may also want to apply this

```diff
diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go
index 20de63fd1..4a4417130 100644
--- a/internal/ingress/controller/controller.go
+++ b/internal/ingress/controller/controller.go
@@ -271,8 +271,6 @@ func (n *NGINXController) CheckWarning(ing *networking.Ingress) ([]string, error
                "influxdb-host",
                "influxdb-server-name",
                "secure-verify-ca-secret",
-               "fastcgi-params-configmap",
-               "fastcgi-index",
        )
 
        // Skip checks if the ingress is marked as deleted
```
k8s-ci-robot: New changes are detected. LGTM label has been removed.
rikatz: /lgtm
/approve
Remove hold as you wish
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/ingress-nginx/pull/10081#issuecomment-1589835662" title="Approved">rikatz</a>*, *<a href="https://github.com/kubernetes/ingress-nginx/pull/10081#" title="Author self-approved">tao12345666333</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fingress-nginx).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes/ingress-nginx/blob/main/OWNERS)~~ [rikatz,tao12345666333]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
tao12345666333: /hold cancel 

Patch:
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index abb2af532..9764a5e74 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -391,6 +391,8 @@ jobs:
               - 'images/custom-error-pages/**'
             cfssl:
               - 'images/cfssl/**'
+            fastcgi-helloserver:
+              - 'images/fastcgi-helloserver/**'
             echo:
               - 'images/echo/**'
             go-grpc-greeter-server:
@@ -410,6 +412,10 @@ jobs:
         if: ${{ steps.filter-images.outputs.cfssl == 'true' }}
         run: |
           cd images/cfssl && make build
+      - name: fastcgi-helloserver
+        if: ${{ steps.filter-images.outputs.fastcgi-helloserver == 'true' }}
+        run: |
+          cd images/fastcgi-helloserver && make build
       - name: echo image build
         if: ${{ steps.filter-images.outputs.echo == 'true' }}
         run: |
diff --git a/MANUAL_RELEASE.md b/MANUAL_RELEASE.md
index 4b4df0ec6..d7144b85d 100644
--- a/MANUAL_RELEASE.md
+++ b/MANUAL_RELEASE.md
@@ -56,6 +56,8 @@
 
   - [cfssl](https://github.com/kubernetes/ingress-nginx/tree/main/images/cfssl)
 
+  - [fastcgi-helloserver](https://github.com/kubernetes/ingress-nginx/tree/main/images/fastcgi-helloserver)
+
   - [httpbin](https://github.com/kubernetes/ingress-nginx/tree/main/images/httpbin)
 
   - [kube-webhook-certgen](https://github.com/kubernetes/ingress-nginx/tree/main/images/kube-webhook-certgen)
diff --git a/docs/kubectl-plugin.md b/docs/kubectl-plugin.md
index 9dc808642..9e5a5dcc6 100644
--- a/docs/kubectl-plugin.md
+++ b/docs/kubectl-plugin.md
@@ -200,6 +200,7 @@ kubectl ingress-nginx conf -n ingress-nginx --host testaddr.local
 
 ```console
 $ kubectl ingress-nginx exec -i -n ingress-nginx -- ls /etc/nginx
+fastcgi_params
 geoip
 lua
 mime.types
diff --git a/docs/user-guide/fcgi-services.md b/docs/user-guide/fcgi-services.md
index d4f1d1f1e..db4d9428b 100644
--- a/docs/user-guide/fcgi-services.md
+++ b/docs/user-guide/fcgi-services.md
@@ -2,10 +2,118 @@
 
 # Exposing FastCGI Servers
 
-**This feature has been removed from Ingress NGINX**
+> **FastCGI** is a [binary protocol](https://en.wikipedia.org/wiki/Binary_protocol "Binary protocol") for interfacing interactive programs with a [web server](https://en.wikipedia.org/wiki/Web_server "Web server"). [...] (It's) aim is to reduce the overhead related to interfacing between web server and CGI programs, allowing a server to handle more web page requests per unit of time.
+>
+> &mdash; Wikipedia
 
-People willing to use fastcgi servers, should create an NGINX + FastCGI service and expose
-this service via Ingress NGINX. 
+The _ingress-nginx_ ingress controller can be used to directly expose [FastCGI](https://en.wikipedia.org/wiki/FastCGI) servers.  Enabling FastCGI in your Ingress only requires setting the _backend-protocol_ annotation to `FCGI`, and with a couple more annotations you can customize the way _ingress-nginx_ handles the communication with your FastCGI _server_.
 
-We recommend using images like `cgr.dev/chainguard/nginx:latest` and expose your fast_cgi application
-as another container on this Pod.
+
+## Example Objects to Expose a FastCGI Pod
+
+The _Pod_ example object below exposes port `9000`, which is the conventional FastCGI port.
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+  name: example-app
+labels:
+  app: example-app
+spec:
+  containers:
+  - name: example-app
+    image: example-app:1.0
+    ports:
+    - containerPort: 9000
+      name: fastcgi
+```
+
+The _Service_ object example below matches port `9000` from the _Pod_ object above.
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+  name: example-service
+spec:
+  selector:
+    app: example-app
+  ports:
+  - port: 9000
+    targetPort: 9000
+    name: fastcgi
+```
+
+And the _Ingress_ and _ConfigMap_ objects below demonstrates the supported _FastCGI_ specific annotations (NGINX actually has 50 FastCGI directives, all of which have not been exposed in the ingress yet), and matches the service `example-service`, and the port named `fastcgi` from above. The _ConfigMap_ **must** be created first for the _Ingress Controller_ to be able to find it when the _Ingress_ object is created, otherwise you will need to restart the _Ingress Controller_ pods.
+
+```yaml
+# The ConfigMap MUST be created first for the ingress controller to be able to
+# find it when the Ingress object is created.
+
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: example-cm
+data:
+  SCRIPT_FILENAME: "/example/index.php"
+
+---
+
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  annotations:
+    nginx.ingress.kubernetes.io/backend-protocol: "FCGI"
+    nginx.ingress.kubernetes.io/fastcgi-index: "index.php"
+    nginx.ingress.kubernetes.io/fastcgi-params-configmap: "example-cm"
+  name: example-app
+spec:
+  ingressClassName: nginx
+  rules:
+  - host: app.example.com
+    http:
+      paths:
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: example-service
+            port:
+              name: fastcgi
+```
+
+## FastCGI Ingress Annotations
+
+To enable FastCGI, the `nginx.ingress.kubernetes.io/backend-protocol` annotation needs to be set to `FCGI`, which overrides the default `HTTP` value.
+
+> `nginx.ingress.kubernetes.io/backend-protocol: "FCGI"`
+
+**This enables the _FastCGI_ mode for all paths defined in the _Ingress_ object**
+
+### The `nginx.ingress.kubernetes.io/fastcgi-index` Annotation
+
+To specify an index file, the `fastcgi-index` annotation value can optionally be set.  In the example below, the value is set to `index.php`.  This annotation corresponds to [the _NGINX_ `fastcgi_index` directive](https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_index).
+
+> `nginx.ingress.kubernetes.io/fastcgi-index: "index.php"`
+
+### The `nginx.ingress.kubernetes.io/fastcgi-params-configmap` Annotation
+
+To specify [_NGINX_ `fastcgi_param` directives](https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_param), the `fastcgi-params-configmap` annotation is used, which in turn must lead to a _ConfigMap_ object containing the _NGINX_ `fastcgi_param` directives as key/values.
+
+> `nginx.ingress.kubernetes.io/fastcgi-params-configmap: "example-configmap"`
+
+And the _ConfigMap_ object to specify the `SCRIPT_FILENAME` and `HTTP_PROXY`  _NGINX's_ `fastcgi_param` directives will look like the following:
+
+```yaml
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: example-configmap
+data:
+  SCRIPT_FILENAME: "/example/index.php"
+  HTTP_PROXY: ""
+```
+Using the _namespace/_ prefix is also supported, for example:
+
+> `nginx.ingress.kubernetes.io/fastcgi-params-configmap: "example-namespace/example-configmap"`
diff --git a/docs/user-guide/nginx-configuration/annotations.md b/docs/user-guide/nginx-configuration/annotations.md
index fc43a47b2..b515a9f3b 100755
--- a/docs/user-guide/nginx-configuration/annotations.md
+++ b/docs/user-guide/nginx-configuration/annotations.md
@@ -894,7 +894,7 @@ Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf
 ### Backend Protocol
 
 Using `backend-protocol` annotations is possible to indicate how NGINX should communicate with the backend service. (Replaces `secure-backends` in older versions)
-Valid Values: HTTP, HTTPS, GRPC, GRPCS, AJP
+Valid Values: HTTP, HTTPS, GRPC, GRPCS, AJP and FCGI
 
 By default NGINX uses `HTTP`.
 
diff --git a/images/fastcgi-helloserver/Makefile b/images/fastcgi-helloserver/Makefile
new file mode 100644
index 000000000..4d277a19d
--- /dev/null
+++ b/images/fastcgi-helloserver/Makefile
@@ -0,0 +1,59 @@
+# Copyright 2020 The Kubernetes Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Docker image for e2e testing.
+
+# set default shell
+SHELL=/bin/bash -o pipefail -o errexit
+
+DIR:=$(strip $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))))
+INIT_BUILDX=$(DIR)/../../hack/init-buildx.sh
+
+SHORT_SHA ?=$(shell git rev-parse --short HEAD)
+TAG ?=v$(shell date +%Y%m%d)-$(SHORT_SHA)
+
+REGISTRY ?= local
+
+IMAGE = $(REGISTRY)/e2e-test-fastcgi-helloserver
+
+# required to enable buildx
+export DOCKER_CLI_EXPERIMENTAL=enabled
+
+# build with buildx
+PLATFORMS?=linux/amd64,linux/arm,linux/arm64
+OUTPUT=
+PROGRESS=plain
+
+build: ensure-buildx
+	docker buildx build \
+		--platform=${PLATFORMS} $(OUTPUT) \
+		--progress=$(PROGRESS) \
+		--pull \
+		-t $(IMAGE):$(TAG) rootfs
+
+# push the cross built image
+push: OUTPUT=--push
+push: build
+
+# enable buildx
+ensure-buildx:
+# this is required for cloudbuild
+ifeq ("$(wildcard $(INIT_BUILDX))","")
+	@curl -sSL https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/hack/init-buildx.sh | bash
+else
+	@exec $(INIT_BUILDX)
+endif
+	@echo "done"
+
+.PHONY: build push ensure-buildx
diff --git a/images/fastcgi-helloserver/cloudbuild.yaml b/images/fastcgi-helloserver/cloudbuild.yaml
new file mode 100644
index 000000000..a4c9d5eff
--- /dev/null
+++ b/images/fastcgi-helloserver/cloudbuild.yaml
@@ -0,0 +1,22 @@
+timeout: 1800s
+options:
+  substitution_option: ALLOW_LOOSE
+steps:
+  - name: gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20211118-2f2d816b90
+    entrypoint: bash
+    env:
+      - DOCKER_CLI_EXPERIMENTAL=enabled
+      - SHORT_SHA=$SHORT_SHA
+      - BASE_REF=$_PULL_BASE_REF
+      - REGISTRY=gcr.io/k8s-staging-ingress-nginx
+      # default cloudbuild has HOME=/builder/home and docker buildx is in /root/.docker/cli-plugins/docker-buildx
+      # set the home to /root explicitly to if using docker buildx
+      - HOME=/root
+    args:
+    - -c
+    - |
+      gcloud auth configure-docker \
+      && cd images/fastcgi-helloserver && make push
+substitutions:
+  _GIT_TAG: "12345"
+  _PULL_BASE_REF: "master"
diff --git a/images/fastcgi-helloserver/rootfs/Dockerfile b/images/fastcgi-helloserver/rootfs/Dockerfile
new file mode 100755
index 000000000..a11834373
--- /dev/null
+++ b/images/fastcgi-helloserver/rootfs/Dockerfile
@@ -0,0 +1,32 @@
+# Copyright 2017 The Kubernetes Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+FROM golang:1.20.4-alpine3.18 as builder
+
+WORKDIR /go/src/k8s.io/ingress-nginx/images/fastcgi
+
+COPY . .
+
+RUN CGO_ENABLED=0 go build -a -installsuffix cgo \
+	-ldflags "-s -w" \
+	-o fastcgi-helloserver main.go
+
+# Use distroless as minimal base image to package the binary
+# Refer to https://github.com/GoogleContainerTools/distroless for more details
+FROM gcr.io/distroless/static:nonroot
+
+COPY --from=builder /go/src/k8s.io/ingress-nginx/images/fastcgi/fastcgi-helloserver /
+USER nonroot:nonroot
+
+CMD ["/fastcgi-helloserver"]
diff --git a/images/fastcgi-helloserver/rootfs/main.go b/images/fastcgi-helloserver/rootfs/main.go
new file mode 100644
index 000000000..91db60c26
--- /dev/null
+++ b/images/fastcgi-helloserver/rootfs/main.go
@@ -0,0 +1,30 @@
+package main
+
+import (
+	"fmt"
+	"net"
+	"net/http"
+	"net/http/fcgi"
+)
+
+func hello(w http.ResponseWriter, r *http.Request) {
+	keys, ok := r.URL.Query()["name"]
+
+	if !ok || len(keys[0]) < 1 {
+		fmt.Fprintf(w, "Hello world!")
+		return
+	}
+
+	key := keys[0]
+	fmt.Fprintf(w, "Hello "+string(key)+"!")
+}
+
+func main() {
+	http.HandleFunc("/hello", hello)
+
+	l, err := net.Listen("tcp", "0.0.0.0:9000")
+	if err != nil {
+		panic(err)
+	}
+	fcgi.Serve(l, nil)
+}
diff --git a/internal/ingress/annotations/annotations.go b/internal/ingress/annotations/annotations.go
index ad7dad37d..5bb2bf5e6 100644
--- a/internal/ingress/annotations/annotations.go
+++ b/internal/ingress/annotations/annotations.go
@@ -41,6 +41,7 @@ import (
 	"k8s.io/ingress-nginx/internal/ingress/annotations/cors"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/customhttperrors"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/defaultbackend"
+	"k8s.io/ingress-nginx/internal/ingress/annotations/fastcgi"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/globalratelimit"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/http2pushpreload"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/ipdenylist"
@@ -85,35 +86,37 @@ type Ingress struct {
 	CorsConfig           cors.Config
 	CustomHTTPErrors     []int
 	DefaultBackend       *apiv1.Service
-	Denied               *string
-	ExternalAuth         authreq.Config
-	EnableGlobalAuth     bool
-	HTTP2PushPreload     bool
-	Opentracing          opentracing.Config
-	Opentelemetry        opentelemetry.Config
-	Proxy                proxy.Config
-	ProxySSL             proxyssl.Config
-	RateLimit            ratelimit.Config
-	GlobalRateLimit      globalratelimit.Config
-	Redirect             redirect.Config
-	Rewrite              rewrite.Config
-	Satisfy              string
-	ServerSnippet        string
-	ServiceUpstream      bool
-	SessionAffinity      sessionaffinity.Config
-	SSLPassthrough       bool
-	UsePortInRedirects   bool
-	UpstreamHashBy       upstreamhashby.Config
-	LoadBalancing        string
-	UpstreamVhost        string
-	Whitelist            ipwhitelist.SourceRange
-	Denylist             ipdenylist.SourceRange
-	XForwardedPrefix     string
-	SSLCipher            sslcipher.Config
-	Logs                 log.Config
-	ModSecurity          modsecurity.Config
-	Mirror               mirror.Config
-	StreamSnippet        string
+	//TODO: Change this back into an error when https://github.com/imdario/mergo/issues/100 is resolved
+	FastCGI            fastcgi.Config
+	Denied             *string
+	ExternalAuth       authreq.Config
+	EnableGlobalAuth   bool
+	HTTP2PushPreload   bool
+	Opentracing        opentracing.Config
+	Opentelemetry      opentelemetry.Config
+	Proxy              proxy.Config
+	ProxySSL           proxyssl.Config
+	RateLimit          ratelimit.Config
+	GlobalRateLimit    globalratelimit.Config
+	Redirect           redirect.Config
+	Rewrite            rewrite.Config
+	Satisfy            string
+	ServerSnippet      string
+	ServiceUpstream    bool
+	SessionAffinity    sessionaffinity.Config
+	SSLPassthrough     bool
+	UsePortInRedirects bool
+	UpstreamHashBy     upstreamhashby.Config
+	LoadBalancing      string
+	UpstreamVhost      string
+	Whitelist          ipwhitelist.SourceRange
+	Denylist           ipdenylist.SourceRange
+	XForwardedPrefix   string
+	SSLCipher          sslcipher.Config
+	Logs               log.Config
+	ModSecurity        modsecurity.Config
+	Mirror             mirror.Config
+	StreamSnippet      string
 }
 
 // Extractor defines the annotation parsers to be used in the extraction of annotations
@@ -135,6 +138,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
 			"CorsConfig":           cors.NewParser(cfg),
 			"CustomHTTPErrors":     customhttperrors.NewParser(cfg),
 			"DefaultBackend":       defaultbackend.NewParser(cfg),
+			"FastCGI":              fastcgi.NewParser(cfg),
 			"ExternalAuth":         authreq.NewParser(cfg),
 			"EnableGlobalAuth":     authreqglobal.NewParser(cfg),
 			"HTTP2PushPreload":     http2pushpreload.NewParser(cfg),
diff --git a/internal/ingress/annotations/backendprotocol/main.go b/internal/ingress/annotations/backendprotocol/main.go
index 0140c30a3..d8ea72386 100644
--- a/internal/ingress/annotations/backendprotocol/main.go
+++ b/internal/ingress/annotations/backendprotocol/main.go
@@ -31,7 +31,7 @@ import (
 const HTTP = "HTTP"
 
 var (
-	validProtocols = regexp.MustCompile(`^(AUTO_HTTP|HTTP|HTTPS|AJP|GRPC|GRPCS)$`)
+	validProtocols = regexp.MustCompile(`^(AUTO_HTTP|HTTP|HTTPS|AJP|GRPC|GRPCS|FCGI)$`)
 )
 
 type backendProtocol struct {
diff --git a/internal/ingress/annotations/fastcgi/main.go b/internal/ingress/annotations/fastcgi/main.go
new file mode 100644
index 000000000..84bac4109
--- /dev/null
+++ b/internal/ingress/annotations/fastcgi/main.go
@@ -0,0 +1,106 @@
+/*
+Copyright 2018 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package fastcgi
+
+import (
+	"fmt"
+	"reflect"
+
+	networking "k8s.io/api/networking/v1"
+	"k8s.io/client-go/tools/cache"
+
+	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
+	ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
+	"k8s.io/ingress-nginx/internal/ingress/resolver"
+)
+
+type fastcgi struct {
+	r resolver.Resolver
+}
+
+// Config describes the per location fastcgi config
+type Config struct {
+	Index  string            `json:"index"`
+	Params map[string]string `json:"params"`
+}
+
+// Equal tests for equality between two Configuration types
+func (l1 *Config) Equal(l2 *Config) bool {
+	if l1 == l2 {
+		return true
+	}
+
+	if l1 == nil || l2 == nil {
+		return false
+	}
+
+	if l1.Index != l2.Index {
+		return false
+	}
+
+	return reflect.DeepEqual(l1.Params, l2.Params)
+}
+
+// NewParser creates a new fastcgiConfig protocol annotation parser
+func NewParser(r resolver.Resolver) parser.IngressAnnotation {
+	return fastcgi{r}
+}
+
+// ParseAnnotations parses the annotations contained in the ingress
+// rule used to indicate the fastcgiConfig.
+func (a fastcgi) Parse(ing *networking.Ingress) (interface{}, error) {
+
+	fcgiConfig := Config{}
+
+	if ing.GetAnnotations() == nil {
+		return fcgiConfig, nil
+	}
+
+	index, err := parser.GetStringAnnotation("fastcgi-index", ing)
+	if err != nil {
+		index = ""
+	}
+	fcgiConfig.Index = index
+
+	cm, err := parser.GetStringAnnotation("fastcgi-params-configmap", ing)
+	if err != nil {
+		return fcgiConfig, nil
+	}
+
+	cmns, cmn, err := cache.SplitMetaNamespaceKey(cm)
+	if err != nil {
+		return fcgiConfig, ing_errors.LocationDenied{
+			Reason: fmt.Errorf("error reading configmap name from annotation: %w", err),
+		}
+	}
+
+	if cmns != "" && cmns != ing.Namespace {
+		return fcgiConfig, fmt.Errorf("different namespace is not supported on fast_cgi param configmap")
+	}
+
+	cm = fmt.Sprintf("%v/%v", ing.Namespace, cmn)
+	cmap, err := a.r.GetConfigMap(cm)
+	if err != nil {
+		return fcgiConfig, ing_errors.LocationDenied{
+			Reason: fmt.Errorf("unexpected error reading configmap %s: %w", cm, err),
+		}
+	}
+
+	fcgiConfig.Params = cmap.Data
+
+	return fcgiConfig, nil
+}
diff --git a/internal/ingress/annotations/parser/main.go b/internal/ingress/annotations/parser/main.go
index 8a0ea4b87..107a278b0 100644
--- a/internal/ingress/annotations/parser/main.go
+++ b/internal/ingress/annotations/parser/main.go
@@ -160,6 +160,7 @@ func normalizeString(input string) string {
 
 var configmapAnnotations = sets.NewString(
 	"auth-proxy-set-header",
+	"fastcgi-params-configmap",
 )
 
 // AnnotationsReferencesConfigmap checks if at least one annotation in the Ingress rule
diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go
index e93d3b5de..4a4417130 100644
--- a/internal/ingress/controller/controller.go
+++ b/internal/ingress/controller/controller.go
@@ -271,8 +271,6 @@ func (n *NGINXController) CheckWarning(ing *networking.Ingress) ([]string, error
 		"influxdb-host",
 		"influxdb-server-name",
 		"secure-verify-ca-secret",
-		"fastcgi-params-configmap",
-		"fastcgi-index",
 	)
 
 	// Skip checks if the ingress is marked as deleted
@@ -608,7 +606,7 @@ func (n *NGINXController) getConfiguration(ingresses []*ingress.Ingress) (sets.S
 
 	for _, server := range servers {
 		// If a location is defined by a prefix string that ends with the slash character, and requests are processed by one of
-		// proxy_pass, uwsgi_pass, scgi_pass, memcached_pass, or grpc_pass, then the special processing is performed.
+		// proxy_pass, fastcgi_pass, uwsgi_pass, scgi_pass, memcached_pass, or grpc_pass, then the special processing is performed.
 		// In response to a request with URI equal to // this string, but without the trailing slash, a permanent redirect with the
 		// code 301 will be returned to the requested URI with the slash appended. If this is not desired, an exact match of the
 		// URIand location could be defined like this:
@@ -1519,6 +1517,7 @@ func locationApplyAnnotations(loc *ingress.Location, anns *annotations.Ingress)
 	loc.Logs = anns.Logs
 	loc.DefaultBackend = anns.DefaultBackend
 	loc.BackendProtocol = anns.BackendProtocol
+	loc.FastCGI = anns.FastCGI
 	loc.CustomHTTPErrors = anns.CustomHTTPErrors
 	loc.ModSecurity = anns.ModSecurity
 	loc.Satisfy = anns.Satisfy
diff --git a/internal/ingress/controller/template/template.go b/internal/ingress/controller/template/template.go
index 15ae438ab..2d941f95d 100644
--- a/internal/ingress/controller/template/template.go
+++ b/internal/ingress/controller/template/template.go
@@ -739,6 +739,9 @@ func buildProxyPass(host string, b interface{}, loc interface{}) string {
 	case "AJP":
 		proto = ""
 		proxyPass = "ajp_pass"
+	case "FCGI":
+		proto = ""
+		proxyPass = "fastcgi_pass"
 	}
 
 	upstreamName := "upstream_balancer"
diff --git a/pkg/apis/ingress/types.go b/pkg/apis/ingress/types.go
index 68cacc25b..e50666c18 100644
--- a/pkg/apis/ingress/types.go
+++ b/pkg/apis/ingress/types.go
@@ -27,6 +27,7 @@ import (
 	"k8s.io/ingress-nginx/internal/ingress/annotations/authtls"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/connection"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/cors"
+	"k8s.io/ingress-nginx/internal/ingress/annotations/fastcgi"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/globalratelimit"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/ipdenylist"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist"
@@ -339,6 +340,9 @@ type Location struct {
 	// BackendProtocol indicates which protocol should be used to communicate with the service
 	// By default this is HTTP
 	BackendProtocol string `json:"backend-protocol"`
+	// FastCGI allows the ingress to act as a FastCGI client for a given location.
+	// +optional
+	FastCGI fastcgi.Config `json:"fastcgi,omitempty"`
 	// CustomHTTPErrors specifies the error codes that should be intercepted.
 	// +optional
 	CustomHTTPErrors []int `json:"custom-http-errors"`
diff --git a/pkg/apis/ingress/types_equals.go b/pkg/apis/ingress/types_equals.go
index fee8c31d5..84b1a186a 100644
--- a/pkg/apis/ingress/types_equals.go
+++ b/pkg/apis/ingress/types_equals.go
@@ -435,6 +435,10 @@ func (l1 *Location) Equal(l2 *Location) bool {
 		return false
 	}
 
+	if !(&l1.FastCGI).Equal(&l2.FastCGI) {
+		return false
+	}
+
 	match := compareInts(l1.CustomHTTPErrors, l2.CustomHTTPErrors)
 	if !match {
 		return false
diff --git a/rootfs/etc/nginx/template/nginx.tmpl b/rootfs/etc/nginx/template/nginx.tmpl
index 638333407..9b3a47de3 100644
--- a/rootfs/etc/nginx/template/nginx.tmpl
+++ b/rootfs/etc/nginx/template/nginx.tmpl
@@ -305,6 +305,7 @@ http {
     keepalive_requests {{ $cfg.KeepAliveRequests }};
 
     client_body_temp_path           /tmp/nginx/client-body;
+    fastcgi_temp_path               /tmp/nginx/fastcgi-temp;
     proxy_temp_path                 /tmp/nginx/proxy-temp;
     ajp_temp_path                   /tmp/nginx/ajp-temp;
 
@@ -1488,6 +1489,16 @@ stream {
             {{ range $errCode := $location.CustomHTTPErrors }}
             error_page {{ $errCode }} = @custom_{{ $location.DefaultBackendUpstreamName }}_{{ $errCode }};{{ end }}
 
+            {{ if (eq $location.BackendProtocol "FCGI") }}
+            include /etc/nginx/fastcgi_params;
+            {{ end }}
+            {{- if $location.FastCGI.Index -}}
+            fastcgi_index {{ $location.FastCGI.Index | quote }};
+            {{- end -}}
+            {{ range $k, $v := $location.FastCGI.Params }}
+            fastcgi_param {{ $k }} {{ $v | quote }};
+            {{ end }}
+
             {{ if not (empty $location.Redirect.URL) }}
             return {{ $location.Redirect.Code }} {{ $location.Redirect.URL }};
             {{ end }}


Test patch:
diff --git a/docs/e2e-tests.md b/docs/e2e-tests.md
index 64d35c79a..025ff686d 100644
--- a/docs/e2e-tests.md
+++ b/docs/e2e-tests.md
@@ -108,6 +108,7 @@ Do not try to edit it manually.
 - [should set backend protocol to $scheme:// and use proxy_pass](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/backendprotocol.go#L49)
 - [should set backend protocol to grpc:// and use grpc_pass](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/backendprotocol.go#L64)
 - [should set backend protocol to grpcs:// and use grpc_pass](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/backendprotocol.go#L79)
+- [should set backend protocol to '' and use fastcgi_pass](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/backendprotocol.go#L94)
 - [should set backend protocol to '' and use ajp_pass](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/backendprotocol.go#L109)
 
 ### [canary-*](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L36)
@@ -191,6 +192,13 @@ Do not try to edit it manually.
 - [disable-http-access-log set access_log off](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/disableaccesslog.go#L53)
 - [disable-stream-access-log set access_log off](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/disableaccesslog.go#L71)
 
+### [backend-protocol - FastCGI](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/fastcgi.go#L30)
+
+- [should use fastcgi_pass in the configuration file](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/fastcgi.go#L37)
+- [should add fastcgi_index in the configuration file](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/fastcgi.go#L54)
+- [should add fastcgi_param in the configuration file](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/fastcgi.go#L71)
+- [should return OK for service with backend protocol FastCGI](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/fastcgi.go#L102)
+
 ### [force-ssl-redirect](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/forcesslredirect.go#L27)
 
 - [should redirect to https](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/forcesslredirect.go#L34)
@@ -342,101 +350,6 @@ Do not try to edit it manually.
 - [should set the X-Forwarded-Prefix to the annotation value](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/xforwardedprefix.go#L35)
 - [should not add X-Forwarded-Prefix if the annotation value is empty](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/xforwardedprefix.go#L57)
 
-### [denylist-source-range](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/ipdenylist.go#L28)
-
-- [only deny explicitly denied IPs, allow all others](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/ipdenylist.go#L35)
-- [only allow explicitly allowed IPs, deny all others](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/ipdenylist.go#L86)
-
-### [affinity session-cookie-name](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L35)
-
-- [should set sticky cookie SERVERID](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L42)
-- [should change cookie name on ingress definition change](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L64)
-- [should set the path to /something on the generated cookie](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L99)
-- [does not set the path to / on the generated cookie if there's more than one rule referring to the same backend](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L121)
-- [should set cookie with expires](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L194)
-- [should set cookie with domain](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L225)
-- [should not set cookie without domain annotation](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L248)
-- [should work with use-regex annotation and session-cookie-path](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L270)
-- [should warn user when use-regex is true and session-cookie-path is not set](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L294)
-- [should not set affinity across all server locations when using separate ingresses](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L320)
-- [should set sticky cookie without host](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L352)
-- [should work with server-alias annotation](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L372)
-- [should set secure in cookie with provided true annotation on http](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L412)
-- [should not set secure in cookie with provided false annotation on http](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L435)
-- [should set secure in cookie with provided false annotation on https](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/affinity.go#L458)
-
-### [rewrite-target use-regex enable-rewrite-log](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/rewrite.go#L30)
-
-- [should write rewrite logs](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/rewrite.go#L37)
-- [should use correct longest path match](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/rewrite.go#L66)
-- [should use ~* location modifier if regex annotation is present](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/rewrite.go#L111)
-- [should fail to use longest match for documented warning](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/rewrite.go#L158)
-- [should allow for custom rewrite parameters](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/rewrite.go#L190)
-
-### [auth-*](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L39)
-
-- [should return status code 200 when no authentication is configured](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L46)
-- [should return status code 503 when authentication is configured with an invalid secret](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L65)
-- [should return status code 401 when authentication is configured but Authorization header is not configured](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L89)
-- [should return status code 401 when authentication is configured and Authorization header is sent with invalid credentials](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L116)
-- [should return status code 401 and cors headers when authentication and cors is configured but Authorization header is not configured](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L144)
-- [should return status code 200 when authentication is configured and Authorization header is sent](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L172)
-- [should return status code 200 when authentication is configured with a map and Authorization header is sent](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L199)
-- [should return status code 401 when authentication is configured with invalid content and Authorization header is sent](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L227)
-- [ when external auth is configured](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L266)
-- [ when external auth is not configured](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L284)
-- [ when auth-headers are set](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L301)
-- [should set cache_key when external auth cache is configured](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L322)
-- [user retains cookie by default](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L411)
-- [user does not retain cookie if upstream returns error status code](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L422)
-- [user with annotated ingress retains cookie if upstream returns error status code](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L433)
-- [should return status code 200 when signed in](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L485)
-- [should redirect to signin url when not signed in](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L494)
-- [keeps processing new ingresses even if one of the existing ingresses is misconfigured](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L505)
-- [should overwrite Foo header with auth response](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L529)
-- [should not create additional upstream block when auth-keepalive is not set](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L552)
-- [should not create additional upstream block when host part of auth-url contains a variable](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L570)
-- [should not create additional upstream block when auth-keepalive is negative](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L590)
-- [should not create additional upstream block when auth-keepalive is set with HTTP/2](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L609)
-- [should create additional upstream block when auth-keepalive is set with HTTP/1.x](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L623)
-- [should return status code 200 when signed in](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L678)
-- [should redirect to signin url when not signed in](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L687)
-- [keeps processing new ingresses even if one of the existing ingresses is misconfigured](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L698)
-- [should return status code 200 when signed in after auth backend is deleted ](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L772)
-- [should deny login for different location on same server](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L792)
-- [should deny login for different servers](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L820)
-- [should redirect to signin url when not signed in](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L849)
-- [should return 503 (location was denied)](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L879)
-- [should add error to the config](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/auth.go#L887)
-
-### [canary-*](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L36)
-
-- [should response with a 200 status from the mainline upstream when requests are made to the mainline ingress](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L48)
-- [should return 404 status for requests to the canary if no matching ingress is found](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L80)
-- [should return the correct status codes when endpoints are unavailable](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L107)
-- [should route requests to the correct upstream if mainline ingress is created before the canary ingress](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L161)
-- [should route requests to the correct upstream if mainline ingress is created after the canary ingress](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L206)
-- [should route requests to the correct upstream if the mainline ingress is modified](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L250)
-- [should route requests to the correct upstream if the canary ingress is modified](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L307)
-- [should route requests to the correct upstream](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L372)
-- [should route requests to the correct upstream](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L426)
-- [should route requests to the correct upstream](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L490)
-- [should route requests to the correct upstream](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L532)
-- [should routes to mainline upstream when the given Regex causes error](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L566)
-- [should route requests to the correct upstream](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L604)
-- [respects always and never values](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L643)
-- [should route requests only to mainline if canary weight is 0](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L705)
-- [should route requests only to canary if canary weight is 100](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L743)
-- [should route requests only to canary if canary weight is equal to canary weight total](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L775)
-- [should route requests split between mainline and canary if canary weight is 50](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L808)
-- [should route requests split between mainline and canary if canary weight is 100 and weight total is 200](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L834)
-- [should not use canary as a catch-all server](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L863)
-- [should not use canary with domain as a server](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L891)
-- [does not crash when canary ingress has multiple paths to the same non-matching backend](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L915)
-- [always routes traffic to canary if first request was affinitized to canary (default behavior)](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L943)
-- [always routes traffic to canary if first request was affinitized to canary (explicit sticky behavior)](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L1000)
-- [routes traffic to either mainline or canary backend (legacy behavior)](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/annotations/canary.go#L1058)
-
 ### [Debug CLI](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/dbg/main.go#L29)
 
 - [should list the backend servers](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/dbg/main.go#L37)
@@ -485,6 +398,10 @@ Do not try to edit it manually.
 
 - [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/exec.go#L)
 
+### [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/fastcgi_helloserver.go#L)
+
+- [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/fastcgi_helloserver.go#L)
+
 ### [[Setting] ](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/framework.go#L194)
 
 - [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/framework.go#L)
@@ -561,30 +478,6 @@ Do not try to edit it manually.
 
 - [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/util.go#L)
 
-### [[Setting] ](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/framework.go#L194)
-
-- [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/framework.go#L)
-
-### [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/healthz.go#L)
-
-- [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/healthz.go#L)
-
-### [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/k8s.go#L)
-
-- [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/k8s.go#L)
-
-### [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/metrics.go#L)
-
-- [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/metrics.go#L)
-
-### [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/deployment.go#L)
-
-- [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/deployment.go#L)
-
-### [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/exec.go#L)
-
-- [](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/framework/exec.go#L)
-
 ### [[Shutdown] Grace period shutdown](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/gracefulshutdown/grace_period.go#L32)
 
 - [/healthz should return status code 500 during shutdown grace period](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/gracefulshutdown/grace_period.go#L35)
diff --git a/internal/ingress/annotations/fastcgi/main_test.go b/internal/ingress/annotations/fastcgi/main_test.go
new file mode 100644
index 000000000..35c5bbc12
--- /dev/null
+++ b/internal/ingress/annotations/fastcgi/main_test.go
@@ -0,0 +1,285 @@
+/*
+Copyright 2018 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package fastcgi
+
+import (
+	"fmt"
+	"testing"
+
+	api "k8s.io/api/core/v1"
+	networking "k8s.io/api/networking/v1"
+	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/tools/cache"
+	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
+	"k8s.io/ingress-nginx/internal/ingress/resolver"
+)
+
+func buildIngress() *networking.Ingress {
+	return &networking.Ingress{
+		ObjectMeta: meta_v1.ObjectMeta{
+			Name:      "foo",
+			Namespace: api.NamespaceDefault,
+		},
+		Spec: networking.IngressSpec{
+			DefaultBackend: &networking.IngressBackend{
+				Service: &networking.IngressServiceBackend{
+					Name: "default-backend",
+					Port: networking.ServiceBackendPort{
+						Number: 80,
+					},
+				},
+			},
+		},
+	}
+}
+
+type mockConfigMap struct {
+	resolver.Mock
+}
+
+func (m mockConfigMap) GetConfigMap(name string) (*api.ConfigMap, error) {
+	if name != "default/demo-configmap" && name != "otherns/demo-configmap" {
+		return nil, fmt.Errorf("there is no configmap with name %v", name)
+	}
+
+	cmns, cmn, err := cache.SplitMetaNamespaceKey(name)
+	if err != nil {
+		return nil, fmt.Errorf("invalid configmap name")
+	}
+
+	return &api.ConfigMap{
+		ObjectMeta: meta_v1.ObjectMeta{
+			Namespace: cmns,
+			Name:      cmn,
+		},
+		Data: map[string]string{"REDIRECT_STATUS": "200", "SERVER_NAME": "$server_name"},
+	}, nil
+}
+
+func TestParseEmptyFastCGIAnnotations(t *testing.T) {
+	ing := buildIngress()
+
+	i, err := NewParser(&mockConfigMap{}).Parse(ing)
+	if err != nil {
+		t.Errorf("unexpected error parsing ingress without fastcgi")
+	}
+
+	config, ok := i.(Config)
+	if !ok {
+		t.Errorf("Parse do not return a Config object")
+	}
+
+	if config.Index != "" {
+		t.Errorf("Index should be an empty string")
+	}
+
+	if len(config.Params) != 0 {
+		t.Errorf("Params should be an empty slice")
+	}
+}
+
+func TestParseFastCGIIndexAnnotation(t *testing.T) {
+	ing := buildIngress()
+
+	const expectedAnnotation = "index.php"
+
+	data := map[string]string{}
+	data[parser.GetAnnotationWithPrefix("fastcgi-index")] = expectedAnnotation
+	ing.SetAnnotations(data)
+
+	i, err := NewParser(&mockConfigMap{}).Parse(ing)
+	if err != nil {
+		t.Errorf("unexpected error parsing ingress without fastcgi")
+	}
+
+	config, ok := i.(Config)
+	if !ok {
+		t.Errorf("Parse do not return a Config object")
+	}
+
+	if config.Index != "index.php" {
+		t.Errorf("expected %s but %v returned", expectedAnnotation, config.Index)
+	}
+}
+
+func TestParseEmptyFastCGIParamsConfigMapAnnotation(t *testing.T) {
+	ing := buildIngress()
+
+	data := map[string]string{}
+	data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = ""
+	ing.SetAnnotations(data)
+
+	i, err := NewParser(&mockConfigMap{}).Parse(ing)
+	if err != nil {
+		t.Errorf("unexpected error parsing ingress without fastcgi")
+	}
+
+	config, ok := i.(Config)
+	if !ok {
+		t.Errorf("Parse do not return a Config object")
+	}
+
+	if len(config.Params) != 0 {
+		t.Errorf("Params should be an empty slice")
+	}
+}
+
+func TestParseFastCGIInvalidParamsConfigMapAnnotation(t *testing.T) {
+	ing := buildIngress()
+
+	invalidConfigMapList := []string{"unknown/configMap", "unknown/config/map"}
+	for _, configmap := range invalidConfigMapList {
+
+		data := map[string]string{}
+		data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = configmap
+		ing.SetAnnotations(data)
+
+		i, err := NewParser(&mockConfigMap{}).Parse(ing)
+		if err == nil {
+			t.Errorf("Reading an unexisting configmap should return an error")
+		}
+
+		config, ok := i.(Config)
+		if !ok {
+			t.Errorf("Parse do not return a Config object")
+		}
+
+		if len(config.Params) != 0 {
+			t.Errorf("Params should be an empty slice")
+		}
+	}
+}
+
+func TestParseFastCGIParamsConfigMapAnnotationWithoutNS(t *testing.T) {
+	ing := buildIngress()
+
+	data := map[string]string{}
+	data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = "demo-configmap"
+	ing.SetAnnotations(data)
+
+	i, err := NewParser(&mockConfigMap{}).Parse(ing)
+	if err != nil {
+		t.Errorf("unexpected error parsing ingress without fastcgi")
+	}
+
+	config, ok := i.(Config)
+	if !ok {
+		t.Errorf("Parse do not return a Config object")
+	}
+
+	if len(config.Params) != 2 {
+		t.Errorf("Params should have a length of 2")
+	}
+
+	if config.Params["REDIRECT_STATUS"] != "200" || config.Params["SERVER_NAME"] != "$server_name" {
+		t.Errorf("Params value is not the one expected")
+	}
+}
+
+func TestParseFastCGIParamsConfigMapAnnotationWithNS(t *testing.T) {
+	ing := buildIngress()
+
+	data := map[string]string{}
+	data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = "default/demo-configmap"
+	ing.SetAnnotations(data)
+
+	i, err := NewParser(&mockConfigMap{}).Parse(ing)
+	if err != nil {
+		t.Errorf("unexpected error parsing ingress without fastcgi")
+	}
+
+	config, ok := i.(Config)
+	if !ok {
+		t.Errorf("Parse do not return a Config object")
+	}
+
+	if len(config.Params) != 2 {
+		t.Errorf("Params should have a length of 2")
+	}
+
+	if config.Params["REDIRECT_STATUS"] != "200" || config.Params["SERVER_NAME"] != "$server_name" {
+		t.Errorf("Params value is not the one expected")
+	}
+}
+
+func TestParseFastCGIParamsConfigMapAnnotationWithDifferentNS(t *testing.T) {
+	ing := buildIngress()
+
+	data := map[string]string{}
+	data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = "otherns/demo-configmap"
+	ing.SetAnnotations(data)
+
+	_, err := NewParser(&mockConfigMap{}).Parse(ing)
+	if err == nil {
+		t.Errorf("Different namespace configmap should return an error")
+	}
+
+}
+
+func TestConfigEquality(t *testing.T) {
+
+	var nilConfig *Config
+
+	config := Config{
+		Index:  "index.php",
+		Params: map[string]string{"REDIRECT_STATUS": "200", "SERVER_NAME": "$server_name"},
+	}
+
+	configCopy := Config{
+		Index:  "index.php",
+		Params: map[string]string{"REDIRECT_STATUS": "200", "SERVER_NAME": "$server_name"},
+	}
+
+	config2 := Config{
+		Index:  "index.php",
+		Params: map[string]string{"REDIRECT_STATUS": "200"},
+	}
+
+	config3 := Config{
+		Index:  "index.py",
+		Params: map[string]string{"SERVER_NAME": "$server_name", "REDIRECT_STATUS": "200"},
+	}
+
+	config4 := Config{
+		Index:  "index.php",
+		Params: map[string]string{"SERVER_NAME": "$server_name", "REDIRECT_STATUS": "200"},
+	}
+
+	if !config.Equal(&config) {
+		t.Errorf("config should be equal to itself")
+	}
+
+	if nilConfig.Equal(&config) {
+		t.Errorf("Foo")
+	}
+
+	if !config.Equal(&configCopy) {
+		t.Errorf("config should be equal to configCopy")
+	}
+
+	if config.Equal(&config2) {
+		t.Errorf("config2 should not be equal to config")
+	}
+
+	if config.Equal(&config3) {
+		t.Errorf("config3 should not be equal to config")
+	}
+
+	if !config.Equal(&config4) {
+		t.Errorf("config4 should be equal to config")
+	}
+}
diff --git a/internal/ingress/controller/controller_test.go b/internal/ingress/controller/controller_test.go
index 355f5da95..c631461c0 100644
--- a/internal/ingress/controller/controller_test.go
+++ b/internal/ingress/controller/controller_test.go
@@ -431,7 +431,7 @@ func TestCheckWarning(t *testing.T) {
 		t.Run("adding invalid annotations increases the warning count", func(t *testing.T) {
 			ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("enable-influxdb")] = "true"
 			ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("secure-verify-ca-secret")] = "true"
-			ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("fastcgi-index")] = "blabla"
+			ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("influxdb-host")] = "blabla"
 			defer func() {
 				ing.ObjectMeta.Annotations = map[string]string{}
 			}()
diff --git a/internal/ingress/controller/template/template_test.go b/internal/ingress/controller/template/template_test.go
index 1b88c3fe7..1980d7e52 100644
--- a/internal/ingress/controller/template/template_test.go
+++ b/internal/ingress/controller/template/template_test.go
@@ -1138,6 +1138,7 @@ func TestOpentracingPropagateContext(t *testing.T) {
 		{BackendProtocol: "GRPC"}:      "opentracing_grpc_propagate_context;",
 		{BackendProtocol: "GRPCS"}:     "opentracing_grpc_propagate_context;",
 		{BackendProtocol: "AJP"}:       "opentracing_propagate_context;",
+		{BackendProtocol: "FCGI"}:      "opentracing_propagate_context;",
 		nil:                            "",
 	}
 
@@ -1157,6 +1158,7 @@ func TestOpentelemetryPropagateContext(t *testing.T) {
 		{BackendProtocol: "GRPC"}:      "opentelemetry_propagate;",
 		{BackendProtocol: "GRPCS"}:     "opentelemetry_propagate;",
 		{BackendProtocol: "AJP"}:       "opentelemetry_propagate;",
+		{BackendProtocol: "FCGI"}:      "opentelemetry_propagate;",
 		nil:                            "",
 	}
 
diff --git a/test/e2e/annotations/backendprotocol.go b/test/e2e/annotations/backendprotocol.go
index 8256befd2..bccb03afb 100644
--- a/test/e2e/annotations/backendprotocol.go
+++ b/test/e2e/annotations/backendprotocol.go
@@ -91,6 +91,21 @@ var _ = framework.DescribeAnnotation("backend-protocol", func() {
 			})
 	})
 
+	ginkgo.It("should set backend protocol to '' and use fastcgi_pass", func() {
+		host := "backendprotocol.foo.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/backend-protocol": "FCGI",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.WaitForNginxServer(host,
+			func(server string) bool {
+				return strings.Contains(server, "fastcgi_pass upstream_balancer;")
+			})
+	})
+
 	ginkgo.It("should set backend protocol to '' and use ajp_pass", func() {
 		host := "backendprotocol.foo.com"
 		annotations := map[string]string{
diff --git a/test/e2e/annotations/fastcgi.go b/test/e2e/annotations/fastcgi.go
new file mode 100644
index 000000000..572eca548
--- /dev/null
+++ b/test/e2e/annotations/fastcgi.go
@@ -0,0 +1,125 @@
+/*
+Copyright 2019 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package annotations
+
+import (
+	"net/http"
+	"strings"
+
+	"github.com/onsi/ginkgo/v2"
+	corev1 "k8s.io/api/core/v1"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/ingress-nginx/test/e2e/framework"
+)
+
+var _ = framework.DescribeAnnotation("backend-protocol - FastCGI", func() {
+	f := framework.NewDefaultFramework("fastcgi")
+
+	ginkgo.BeforeEach(func() {
+		f.NewFastCGIHelloServerDeployment()
+	})
+
+	ginkgo.It("should use fastcgi_pass in the configuration file", func() {
+		host := "fastcgi"
+
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/backend-protocol": "FCGI",
+		}
+
+		ing := framework.NewSingleIngress(host, "/hello", host, f.Namespace, "fastcgi-helloserver", 9000, annotations)
+		f.EnsureIngress(ing)
+
+		f.WaitForNginxServer(host,
+			func(server string) bool {
+				return strings.Contains(server, "include /etc/nginx/fastcgi_params;") &&
+					strings.Contains(server, "fastcgi_pass")
+			})
+	})
+
+	ginkgo.It("should add fastcgi_index in the configuration file", func() {
+		host := "fastcgi-index"
+
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/backend-protocol": "FCGI",
+			"nginx.ingress.kubernetes.io/fastcgi-index":    "index.php",
+		}
+
+		ing := framework.NewSingleIngress(host, "/hello", host, f.Namespace, "fastcgi-helloserver", 9000, annotations)
+		f.EnsureIngress(ing)
+
+		f.WaitForNginxServer(host,
+			func(server string) bool {
+				return strings.Contains(server, "fastcgi_index \"index.php\";")
+			})
+	})
+
+	ginkgo.It("should add fastcgi_param in the configuration file", func() {
+		configuration := &corev1.ConfigMap{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "fastcgi-configmap",
+				Namespace: f.Namespace,
+			},
+			Data: map[string]string{
+				"SCRIPT_FILENAME": "/home/www/scripts/php$fastcgi_script_name",
+				"REDIRECT_STATUS": "200",
+			},
+		}
+
+		f.EnsureConfigMap(configuration)
+
+		host := "fastcgi-params-configmap"
+
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/backend-protocol":         "FCGI",
+			"nginx.ingress.kubernetes.io/fastcgi-params-configmap": "fastcgi-configmap",
+		}
+
+		ing := framework.NewSingleIngress(host, "/hello", host, f.Namespace, "fastcgi-helloserver", 9000, annotations)
+		f.EnsureIngress(ing)
+
+		f.WaitForNginxServer(host,
+			func(server string) bool {
+				return strings.Contains(server, "fastcgi_param SCRIPT_FILENAME \"/home/www/scripts/php$fastcgi_script_name\";") &&
+					strings.Contains(server, "fastcgi_param REDIRECT_STATUS \"200\";")
+			})
+	})
+
+	ginkgo.It("should return OK for service with backend protocol FastCGI", func() {
+		host := "fastcgi-helloserver"
+		path := "/hello"
+
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/backend-protocol": "FCGI",
+		}
+
+		ing := framework.NewSingleIngress(host, path, host, f.Namespace, "fastcgi-helloserver", 9000, annotations)
+		f.EnsureIngress(ing)
+
+		f.WaitForNginxServer(host,
+			func(server string) bool {
+				return strings.Contains(server, "fastcgi_pass")
+			})
+
+		f.HTTPTestClient().
+			GET(path).
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusOK).
+			Body().Contains("Hello world!")
+	})
+})
diff --git a/test/e2e/framework/fastcgi_helloserver.go b/test/e2e/framework/fastcgi_helloserver.go
new file mode 100644
index 000000000..719048c06
--- /dev/null
+++ b/test/e2e/framework/fastcgi_helloserver.go
@@ -0,0 +1,104 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package framework
+
+import (
+	"github.com/onsi/ginkgo/v2"
+	"github.com/stretchr/testify/assert"
+	appsv1 "k8s.io/api/apps/v1"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/fields"
+	"k8s.io/apimachinery/pkg/util/intstr"
+)
+
+// NewFastCGIHelloServerDeployment creates a new single replica
+// deployment of the fortune teller image in a particular namespace
+func (f *Framework) NewFastCGIHelloServerDeployment() {
+	f.NewNewFastCGIHelloServerDeploymentWithReplicas(1)
+}
+
+// NewNewFastCGIHelloServerDeploymentWithReplicas creates a new deployment of the
+// fortune teller image in a particular namespace. Number of replicas is configurable
+func (f *Framework) NewNewFastCGIHelloServerDeploymentWithReplicas(replicas int32) {
+	deployment := &appsv1.Deployment{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "fastcgi-helloserver",
+			Namespace: f.Namespace,
+		},
+		Spec: appsv1.DeploymentSpec{
+			Replicas: NewInt32(replicas),
+			Selector: &metav1.LabelSelector{
+				MatchLabels: map[string]string{
+					"app": "fastcgi-helloserver",
+				},
+			},
+			Template: corev1.PodTemplateSpec{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels: map[string]string{
+						"app": "fastcgi-helloserver",
+					},
+				},
+				Spec: corev1.PodSpec{
+					TerminationGracePeriodSeconds: NewInt64(0),
+					Containers: []corev1.Container{
+						{
+							Name:  "fastcgi-helloserver",
+							Image: "registry.k8s.io/ingress-nginx/e2e-test-fastcgi-helloserver@sha256:0e08c836cc58f1ea862578de99b13bc4264fe071e816f96dc1d79857bfba7473",
+							Env:   []corev1.EnvVar{},
+							Ports: []corev1.ContainerPort{
+								{
+									Name:          "fastcgi",
+									ContainerPort: 9000,
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	d := f.EnsureDeployment(deployment)
+
+	err := waitForPodsReady(f.KubeClientSet, DefaultTimeout, int(replicas), f.Namespace, metav1.ListOptions{
+		LabelSelector: fields.SelectorFromSet(fields.Set(d.Spec.Template.ObjectMeta.Labels)).String(),
+	})
+	assert.Nil(ginkgo.GinkgoT(), err, "failed to wait for to become ready")
+
+	service := &corev1.Service{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "fastcgi-helloserver",
+			Namespace: f.Namespace,
+		},
+		Spec: corev1.ServiceSpec{
+			Ports: []corev1.ServicePort{
+				{
+					Name:       "fastcgi",
+					Port:       9000,
+					TargetPort: intstr.FromInt(9000),
+					Protocol:   "TCP",
+				},
+			},
+			Selector: map[string]string{
+				"app": "fastcgi-helloserver",
+			},
+		},
+	}
+
+	f.EnsureService(service)
+}