Repository: kubernetes/ingress-nginx
Pull request #7603 (2021-09-06 17:14:33)

additional info for the custom-headers documentation page

## What this PR does / why we need it:
According to the documentation here: https://kubernetes.github.io/ingress-nginx/examples/customization/custom-headers/ , proxy-set-headers should cite the custom-headers configmap, therefore include all the custom headers in the requests.

In my case I needed the custom-headers to be passed before sending the response to the client. As the documentation's title is general (regarding custom-headers), I. think it would be a bit more clear to have both cases as an example


## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## Which issue/s this PR fixes
<!--
(optional, in `fixes #<issue number>` format, will close that issue when PR gets merged):

fixes #7566
-->

## How Has This Been Tested?
I have used the Markdown Preview
I tested ifI can read :
I checked if the https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/docs/examples/customization/custom-headers/configmap-client-response.yaml file has the right value (add-headers)



## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [x] My change requires a change to the documentation.
- [x] I have updated the documentation accordingly.
- [x] I've read the [CONTRIBUTION](https://github.com/kubernetes/ingress-nginx/blob/main/CONTRIBUTING.md) guide
- [ ] I have added tests to cover my changes.
- [x] All new and existing tests passed.


k8s-ci-robot: Thanks for your pull request. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

:memo: **Please follow instructions at <https://git.k8s.io/community/CLA.md#the-contributor-license-agreement> to sign the CLA.**

It may take a couple minutes for the CLA signature to be fully registered; after that, please reply here with a new comment and we'll verify.  Thanks.

---

- If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address.  Check your existing CLA data and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).
- If you signed the CLA as a corporation, please sign in with your organization's credentials at <https://identity.linuxfoundation.org/projects/cncf> to be authorized.
- If you have done the above and are still having issues with the CLA being reported as unsigned, please log a ticket with the Linux Foundation Helpdesk: <https://support.linuxfoundation.org/>
- Should you encounter any issues with the Linux Foundation Helpdesk, send a message to the backup e-mail support address at: login-issues@jira.linuxfoundation.org

<!-- need_sender_cla -->

<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. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
	
k8s-ci-robot: @iugastefan922: 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: Hi @iugastefan922. Thanks for your PR.

I'm waiting for a [kubernetes](https://github.com/orgs/kubernetes/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fingress-nginx).

<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 **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/ingress-nginx/pull/7603#" title="Author self-approved">iugastefan922</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **justinsb** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @justinsb` in a comment when ready.

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

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

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

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":["justinsb"]} -->
iugastefan922: I signed it!

Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 3df73cc6a..c39d6e2c2 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -27,7 +27,13 @@ This questions are the first thing we need to know to understand the context.
 
 -->
 
-**NGINX Ingress controller version**:
+**NGINX Ingress controller version** (exec into the pod and run nginx-ingress-controller --version.):
+<!--
+POD_NAMESPACE=ingress-nginx
+POD_NAME=$(kubectl get pods -n $POD_NAMESPACE -l app.kubernetes.io/name=ingress-nginx --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}')
+
+kubectl exec -it $POD_NAME -n $POD_NAMESPACE -- /nginx-ingress-controller --version  
+-->
 
 **Kubernetes version** (use `kubectl version`):
 
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 703be9cb3..030c9abe5 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -122,7 +122,7 @@ jobs:
 
       - name: Checkout
         uses: actions/checkout@v2
-    
+
       - name: cache
         uses: actions/download-artifact@v2
         with:
@@ -143,7 +143,7 @@ jobs:
         with:
           version: v0.11.1
           image: kindest/node:v1.21.1
-    
+
       - uses: geekyeggo/delete-artifact@v1
         with:
           name: docker.tar.gz
@@ -220,3 +220,60 @@ jobs:
         run: |
           kind get kubeconfig > $HOME/.kube/kind-config-kind
           make kind-e2e-test
+
+  test-image-build:
+    runs-on: ubuntu-latest
+    env:
+      PLATFORMS: linux/amd64
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - uses: dorny/paths-filter@v2
+        id: filter-images
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
+          filters: |
+            custom-error-pages:
+              - 'images/custom-error-pages/**'
+            cfssl:
+              - 'images/cfssl/**'
+            fastcgi-helloserver:
+              - 'images/fastcgi-helloserver/**'
+            echo:
+              - 'images/echo/**'
+            go-grpc-greeter-server:
+              - 'images/go-grpc-greeter-server/**'
+            httpbin:
+              - 'images/httpbin/**'
+            kube-webhook-certgen:
+              - 'images/kube-webhook-certgen/**'
+
+      - name: custom-error-pages image build
+        if: ${{ steps.filter-images.outputs.custom-error-pages == 'true' }}
+        run: |
+          cd images/custom-error-pages && make build
+      - name: cfssl image build
+        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: |
+          cd images/echo && make build
+      - name: go-grpc-greeter-server image build
+        if: ${{ steps.filter-images.outputs.go-grpc-greeter-server == 'true' }}
+        run: |
+          cd images/go-grpc-greeter-server && make build
+      - name: httpbin image build
+        if: ${{ steps.filter-images.outputs.httpbin == 'true' }}
+        run: |
+          cd images/httpbin && make build
+      - name: kube-webhook-certgen image build
+        if: ${{ steps.filter-images.outputs.kube-webhook-certgen == 'true' }}
+        run: |
+          cd images/kube-webhook-certgen && make build
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 6bc6f8cdc..59bd3c7fe 100644
--- a/Makefile
+++ b/Makefile
@@ -51,7 +51,7 @@ endif
 
 REGISTRY ?= gcr.io/k8s-staging-ingress-nginx
 
-BASE_IMAGE ?= k8s.gcr.io/ingress-nginx/nginx:v20210904-gb7c973dce@sha256:fac972a7e43b18408ecb9e87da868df519428294e2e988c16be72479ee873c0e
+BASE_IMAGE ?= k8s.gcr.io/ingress-nginx/nginx:v20210915-g498892514@sha256:8c1e48123e64e3f2b90ed32a53babd9b5f5431dad26beecdcb8fc185ded3b6dd
 
 GOARCH=$(ARCH)
 
diff --git a/SECURITY_CONTACTS b/SECURITY_CONTACTS
index fd437b717..b04c993fc 100644
--- a/SECURITY_CONTACTS
+++ b/SECURITY_CONTACTS
@@ -9,4 +9,6 @@
 #
 # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
 # INSTRUCTIONS AT https://kubernetes.io/security/
-
+bowei
+rikatz
+strongjz
diff --git a/build/run-in-docker.sh b/build/run-in-docker.sh
index debbe9752..d345a81ec 100755
--- a/build/run-in-docker.sh
+++ b/build/run-in-docker.sh
@@ -37,7 +37,7 @@ function cleanup {
 }
 trap cleanup EXIT
 
-E2E_IMAGE=${E2E_IMAGE:-k8s.gcr.io/ingress-nginx/e2e-test-runner:v20210822-g5e5faa24d@sha256:55c568d9e35e15d94b3ab41fe549b8ee4cd910cc3e031ddcccd06256755c5d89}
+E2E_IMAGE=${E2E_IMAGE:-k8s.gcr.io/ingress-nginx/e2e-test-runner:v20210916-gd9f96bbbb@sha256:5b434c08e582b58b96867152682c1e754ee609c82390abf3074992d4ec53ed25}
 
 DOCKER_OPTS=${DOCKER_OPTS:-}
 DOCKER_IN_DOCKER_ENABLED=${DOCKER_IN_DOCKER_ENABLED:-}
diff --git a/charts/ingress-nginx/README.md b/charts/ingress-nginx/README.md
index 22e66fb85..fecbbcde2 100644
--- a/charts/ingress-nginx/README.md
+++ b/charts/ingress-nginx/README.md
@@ -84,7 +84,8 @@ else it would make it impossible to evacuate a node. See [gh issue #7127](https:
 
 The Nginx ingress controller can export Prometheus metrics, by setting `controller.metrics.enabled` to `true`.
 
-You can add Prometheus annotations to the metrics service using `controller.metrics.service.annotations`. Alternatively, if you use the Prometheus Operator, you can enable ServiceMonitor creation using `controller.metrics.serviceMonitor.enabled`.
+You can add Prometheus annotations to the metrics service using `controller.metrics.service.annotations`.
+Alternatively, if you use the Prometheus Operator, you can enable ServiceMonitor creation using `controller.metrics.serviceMonitor.enabled`. And set `controller.metrics.serviceMonitor.additionalLabels.release="prometheus"`. "release=prometheus" should match the label configured in the prometheus servicemonitor ( see `kubectl get servicemonitor prometheus-kube-prom-prometheus -oyaml -n prometheus`)
 
 ### ingress-nginx nginx\_status page/stats server
 
diff --git a/charts/ingress-nginx/templates/NOTES.txt b/charts/ingress-nginx/templates/NOTES.txt
index 2eebba368..03ece9c59 100644
--- a/charts/ingress-nginx/templates/NOTES.txt
+++ b/charts/ingress-nginx/templates/NOTES.txt
@@ -33,7 +33,7 @@ An example Ingress that makes use of the controller:
   kind: Ingress
   metadata:
     annotations:
-      kubernetes.io/ingress.class: {{ .Values.controller.ingressClass }}
+      kubernetes.io/ingress.class: {{ .Values.controller.ingressClassResource.name }}
     name: example
     namespace: foo
   spec:
diff --git a/charts/ingress-nginx/templates/controller-daemonset.yaml b/charts/ingress-nginx/templates/controller-daemonset.yaml
index 2e6b32170..68291edd7 100644
--- a/charts/ingress-nginx/templates/controller-daemonset.yaml
+++ b/charts/ingress-nginx/templates/controller-daemonset.yaml
@@ -114,6 +114,9 @@ spec:
           {{- if .Values.controller.healthCheckHost }}
             - --healthz-host={{ .Values.controller.healthCheckHost }}
           {{- end }}
+          {{- if .Values.controller.ingressClassByName }}
+            - --ingress-class-by-name=true
+          {{- end }}
           {{- if .Values.controller.watchIngressWithoutClass }}
             - --watch-ingress-without-class=true
           {{- end }}
diff --git a/charts/ingress-nginx/templates/controller-deployment.yaml b/charts/ingress-nginx/templates/controller-deployment.yaml
index 681955f6f..24714a523 100644
--- a/charts/ingress-nginx/templates/controller-deployment.yaml
+++ b/charts/ingress-nginx/templates/controller-deployment.yaml
@@ -115,6 +115,9 @@ spec:
           {{- if not (eq .Values.controller.healthCheckPath "/healthz") }}
             - --health-check-path={{ .Values.controller.healthCheckPath }}
           {{- end }}
+          {{- if .Values.controller.ingressClassByName }}
+            - --ingress-class-by-name=true
+          {{- end }}
           {{- if .Values.controller.watchIngressWithoutClass }}
             - --watch-ingress-without-class=true
           {{- end }}
diff --git a/charts/ingress-nginx/values.yaml b/charts/ingress-nginx/values.yaml
index 8ef8ea8e7..dff440313 100644
--- a/charts/ingress-nginx/values.yaml
+++ b/charts/ingress-nginx/values.yaml
@@ -66,6 +66,9 @@ controller:
   # Defaults to false
   watchIngressWithoutClass: false
 
+  # Process IngressClass per name (additionally as per spec.controller)
+  ingressClassByName: false
+
   # Required for use with CNI based kubernetes installations (such as ones set up by kubeadm),
   # since CNI and hostport don't mix yet. Can be deprecated once https://github.com/kubernetes/kubernetes/issues/23920
   # is merged
diff --git a/cmd/nginx/flags.go b/cmd/nginx/flags.go
index 7e6db6533..42c14dd51 100644
--- a/cmd/nginx/flags.go
+++ b/cmd/nginx/flags.go
@@ -68,6 +68,9 @@ referenced in an Ingress Object should be the same value specified here to make
 		watchWithoutClass = flags.Bool("watch-ingress-without-class", false,
 			`Define if Ingress Controller should also watch for Ingresses without an IngressClass or the annotation specified`)
 
+		ingressClassByName = flags.Bool("ingress-class-by-name", false,
+			`Define if Ingress Controller should watch for Ingress Class by Name together with Controller Class`)
+
 		configMap = flags.String("configmap", "",
 			`Name of the ConfigMap containing custom global configurations for the controller.`)
 
@@ -174,6 +177,8 @@ Takes the form "<host>:port". If not provided, no admission controller is starte
 			`The path of the validating webhook certificate PEM.`)
 		validationWebhookKey = flags.String("validating-webhook-key", "",
 			`The path of the validating webhook key PEM.`)
+		disableFullValidationTest = flags.Bool("disable-full-test", false,
+			`Disable full test of all merged ingresses at the admission stage and tests the template of the ingress being created or updated  (full test of all ingresses is enabled by default)`)
 
 		statusPort = flags.Int("status-port", 10246, `Port to use for the lua HTTP endpoint configuration.`)
 		streamPort = flags.Int("stream-port", 10247, "Port to use for the lua TCP/UDP endpoint configuration.")
@@ -280,6 +285,7 @@ https://blog.maxmind.com/2019/12/18/significant-changes-to-accessing-and-using-g
 		ConfigMapName:              *configMap,
 		TCPConfigMapName:           *tcpConfigMapName,
 		UDPConfigMapName:           *udpConfigMapName,
+		DisableFullValidationTest:  *disableFullValidationTest,
 		DefaultSSLCertificate:      *defSSLCertificate,
 		PublishService:             *publishSvc,
 		PublishStatusAddress:       *publishStatusAddress,
@@ -296,9 +302,10 @@ https://blog.maxmind.com/2019/12/18/significant-changes-to-accessing-and-using-g
 			SSLProxy: *sslProxyPort,
 		},
 		IngressClassConfiguration: &ingressclass.IngressClassConfiguration{
-			Controller:        *ingressClassController,
-			AnnotationValue:   *ingressClassAnnotation,
-			WatchWithoutClass: *watchWithoutClass,
+			Controller:         *ingressClassController,
+			AnnotationValue:    *ingressClassAnnotation,
+			WatchWithoutClass:  *watchWithoutClass,
+			IngressClassByName: *ingressClassByName,
 		},
 		DisableCatchAll:           *disableCatchAll,
 		ValidationWebhook:         *validationWebhook,
diff --git a/docs/examples/customization/custom-errors/custom-default-backend.yaml b/docs/examples/customization/custom-errors/custom-default-backend.yaml
index 70096bdbe..579e48544 100644
--- a/docs/examples/customization/custom-errors/custom-default-backend.yaml
+++ b/docs/examples/customization/custom-errors/custom-default-backend.yaml
@@ -36,7 +36,7 @@ spec:
     spec:
       containers:
       - name: nginx-error-server
-        image: quay.io/kubernetes-ingress-controller/custom-error-pages-amd64:0.3
+        image: k8s.gcr.io/ingress-nginx/nginx-errors:0.48.1
         ports:
         - containerPort: 8080
         # Setting the environment variable DEBUG we can see the headers sent 
diff --git a/docs/examples/customization/custom-headers/README.md b/docs/examples/customization/custom-headers/README.md
index ac43499e9..378f2aa87 100644
--- a/docs/examples/customization/custom-headers/README.md
+++ b/docs/examples/customization/custom-headers/README.md
@@ -18,7 +18,15 @@ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main
 
 The nginx ingress controller will read the `ingress-nginx/ingress-nginx-controller` ConfigMap, find the `proxy-set-headers` key, read HTTP headers from the `ingress-nginx/custom-headers` ConfigMap, and include those HTTP headers in all requests flowing from nginx to the backends.
 
+
+The above example was for passing a custom list of headers to the upstream server.
+To pass the custom headers before sending response traffic to the client, use the add-headers key:
+
+```console
+kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/docs/examples/customization/custom-headers/configmap-client-response.yaml
+```
+
 ## Test
 
 Check the contents of the ConfigMaps are present in the nginx.conf file using:
-`kubectl exec ingress-nginx-controller-873061567-4n3k2 -n ingress-nginx -- cat /etc/nginx/nginx.conf`
+`kubectl exec ingress-nginx-controller-873061567-4n3k2 -n ingress-nginx -- cat /etc/nginx/nginx.conf`
\ No newline at end of file
diff --git a/docs/examples/customization/custom-headers/configmap-client-response.yaml b/docs/examples/customization/custom-headers/configmap-client-response.yaml
new file mode 100644
index 000000000..3213de3f2
--- /dev/null
+++ b/docs/examples/customization/custom-headers/configmap-client-response.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+data:
+  add-headers: "ingress-nginx/custom-headers"
+kind: ConfigMap
+metadata:
+  name: ingress-nginx-controller
+  namespace: ingress-nginx
+  labels:
+    app.kubernetes.io/name: ingress-nginx
+    app.kubernetes.io/part-of: ingress-nginx 
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
index cdcd4f896..8eb626101 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -104,7 +104,7 @@ DESCRIPTION:
 
 - If you have only one instance of the Ingress-NGINX controller running in your cluster, and you still want to use ingressclass, you should add the annotation "ingressclass.kubernetes.io/is-default-class" in your ingress class, so any new Ingress objects will have this one as default ingressClass.
 
-In this case, you need to make your Controller aware of the objects. If you have several Ingress objects and they don't yet have the [ingressClassName](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#ingress-v1-networking-k8s-io) field, or the ingress annotation (`kubernetes.io/ingress.class`),  then you should start your ingress-controller with the flag [--watch-ingress-without-class=true](## What is the flag '--watch-without-ingress-class' ?) .
+In this case, you need to make your Controller aware of the objects. If you have several Ingress objects and they don't yet have the [ingressClassName](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#ingress-v1-networking-k8s-io) field, or the ingress annotation (`kubernetes.io/ingress.class`),  then you should start your ingress-controller with the flag [--watch-ingress-without-class=true](## What is the flag '--watch-ingress-without-class' ?) .
 
 You can configure your helm chart installation's values file with `.controller.watchIngressWithoutClass: true`. 
 
@@ -126,7 +126,7 @@ And add the value "spec.ingressClassName=nginx" in your Ingress objects
 ## I have multiple ingress objects in my cluster. What should I do ?
 - If you don't care about ingressClass, or you have a lot of ingress objects without ingressClass configuration, you can run the ingress-controller with the flag `--watch-ingress-without-class=true`.
 
-## What is the flag '--watch-without-ingress-class' ?
+## What is the flag '--watch-ingress-without-class' ?
 - Its a flag that is passed,as an argument, to the ingress-controller executable, in the pod spec. It looks like this ;
 ```
 ...
diff --git a/docs/user-guide/cli-arguments.md b/docs/user-guide/cli-arguments.md
index 9c9ce0a9b..ef1c0feb2 100644
--- a/docs/user-guide/cli-arguments.md
+++ b/docs/user-guide/cli-arguments.md
@@ -16,6 +16,7 @@ They are set in the container spec of the `nginx-ingress-controller` Deployment
 | `--default-server-port`            | Port to use for exposing the default server (catch-all). (default 8181) |
 | `--default-ssl-certificate`        | Secret containing a SSL certificate to be used by the default HTTPS server (catch-all). Takes the form "namespace/name". |
 | `--disable-catch-all`              | Disable support for catch-all Ingresses |
+| `--disable-full-test` | Disable full test of all merged ingresses at the admission stage and tests the template of the ingress being created or updated  (full test of all ingresses is enabled by default) |
 | `--election-id`                    | Election id to use for Ingress status updates. (default "ingress-controller-leader") |
 | `--enable-metrics`                 | Enables the collection of NGINX metrics (default true) |
 | `--enable-ssl-chain-completion`    | Autocomplete SSL certificate chains with missing intermediate CA certificates. Certificates uploaded to Kubernetes must have the "Authority Information Access" X.509 v3 extension for this to succeed. |
diff --git a/docs/user-guide/nginx-configuration/annotations.md b/docs/user-guide/nginx-configuration/annotations.md
index e9fefebf1..38439b33f 100755
--- a/docs/user-guide/nginx-configuration/annotations.md
+++ b/docs/user-guide/nginx-configuration/annotations.md
@@ -244,23 +244,18 @@ It is possible to enable Client Certificate Authentication using additional anno
 
 Client Certificate Authentication is applied per host and it is not possible to specify rules that differ for individual paths.
 
-The annotations are:
+To enable, add the annotation `nginx.ingress.kubernetes.io/auth-tls-secret: namespace/secretName`. This secret must have a file named `ca.crt` containing the full Certificate Authority chain `ca.crt` that is enabled to authenticate against this Ingress.
 
-* `nginx.ingress.kubernetes.io/auth-tls-secret: secretName`:
-  The name of the Secret that contains the full Certificate Authority chain `ca.crt` that is enabled to authenticate against this Ingress.
-  This annotation expects the Secret name in the form "namespace/secretName".
-* `nginx.ingress.kubernetes.io/auth-tls-verify-depth`:
-  The validation depth between the provided client certificate and the Certification Authority chain.
-* `nginx.ingress.kubernetes.io/auth-tls-verify-client`:
-  Enables verification of client certificates. Possible values are:
-  * `off`: Don't request client certificates and don't do client certificate verification. (default)
-  * `on`: Request a client certificate that must be signed by a certificate that is included in the secret key `ca.crt` of the secret specified by `nginx.ingress.kubernetes.io/auth-tls-secret: secretName`. Failed certificate verification will result in a status code 400 (Bad Request).
-  * `optional`: Do optional client certificate validation against the CAs from `auth-tls-secret`. The request fails with status code 400 (Bad Request) when a certificate is provided that is not signed by the CA. When no or an otherwise invalid certificate is provided, the request does not fail, but instead the verification result is sent to the upstream service.
-  * `optional_no_ca`: Do optional client certificate validation, but do not fail the request when the client certificate is not signed by the CAs from `auth-tls-secret`. Certificate verification result is sent to the upstream service.
-* `nginx.ingress.kubernetes.io/auth-tls-error-page`:
-  The URL/Page that user should be redirected in case of a Certificate Authentication Error
-* `nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream`:
-  Indicates if the received certificates should be passed or not to the upstream server in the header `ssl-client-cert`. Possible values are "true" or "false" (default).
+You can further customize client certificate authentication and behaviour with these annotations:
+
+* `nginx.ingress.kubernetes.io/auth-tls-verify-depth`: The validation depth between the provided client certificate and the Certification Authority chain. (default: 1)
+* `nginx.ingress.kubernetes.io/auth-tls-verify-client`: Enables verification of client certificates. Possible values are:
+    * `on`: Request a client certificate that must be signed by a certificate that is included in the secret key `ca.crt` of the secret specified by `nginx.ingress.kubernetes.io/auth-tls-secret: namespace/secretName`. Failed certificate verification will result in a status code 400 (Bad Request) (default)
+    * `off`: Don't request client certificates and don't do client certificate verification.
+    * `optional`: Do optional client certificate validation against the CAs from `auth-tls-secret`. The request fails with status code 400 (Bad Request) when a certificate is provided that is not signed by the CA. When no or an otherwise invalid certificate is provided, the request does not fail, but instead the verification result is sent to the upstream service.
+    * `optional_no_ca`: Do optional client certificate validation, but do not fail the request when the client certificate is not signed by the CAs from `auth-tls-secret`. Certificate verification result is sent to the upstream service.
+* `nginx.ingress.kubernetes.io/auth-tls-error-page`: The URL/Page that user should be redirected in case of a Certificate Authentication Error
+* `nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream`: Indicates if the received certificates should be passed or not to the upstream server in the header `ssl-client-cert`. Possible values are "true" or "false" (default).
 
 The following headers are sent to the upstream service according to the `auth-tls-*` annotations:
 
@@ -333,39 +328,43 @@ location enabling this functionality.
 
 CORS can be controlled with the following annotations:
 
-* `nginx.ingress.kubernetes.io/cors-allow-methods`
-  controls which methods are accepted. This is a multi-valued field, separated by ',' and
-  accepts only letters (upper and lower case).
-  - Default: `GET, PUT, POST, DELETE, PATCH, OPTIONS`
-  - Example: `nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, OPTIONS"`
-
-* `nginx.ingress.kubernetes.io/cors-allow-headers`
-  controls which headers are accepted. This is a multi-valued field, separated by ',' and accepts letters,
-  numbers, _ and -.
-  - Default: `DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization`
-  - Example: `nginx.ingress.kubernetes.io/cors-allow-headers: "X-Forwarded-For, X-app123-XPTO"`
-
-* `nginx.ingress.kubernetes.io/cors-expose-headers`
-  controls which headers are exposed to response. This is a multi-valued field, separated by ',' and accepts
-  letters, numbers, _, - and *.
-  - Default: *empty*
-  - Example: `nginx.ingress.kubernetes.io/cors-expose-headers: "*, X-CustomResponseHeader"`
-
-* `nginx.ingress.kubernetes.io/cors-allow-origin`
-  controls what's the accepted Origin for CORS.
-  This is a single field value, with the following format: `http(s)://origin-site.com` or `http(s)://origin-site.com:port`
-  - Default: `*`
-  - Example: `nginx.ingress.kubernetes.io/cors-allow-origin: "https://origin-site.com:4443"`
-
-* `nginx.ingress.kubernetes.io/cors-allow-credentials`
-  controls if credentials can be passed during CORS operations.
-  - Default: `true`
-  - Example: `nginx.ingress.kubernetes.io/cors-allow-credentials: "false"`
-
-* `nginx.ingress.kubernetes.io/cors-max-age`
-  controls how long preflight requests can be cached.
-  Default: `1728000`
-  Example: `nginx.ingress.kubernetes.io/cors-max-age: 600`
+* `nginx.ingress.kubernetes.io/cors-allow-methods`: Controls which methods are accepted.
+
+    This is a multi-valued field, separated by ',' and accepts only letters (upper and lower case).
+
+    - Default: `GET, PUT, POST, DELETE, PATCH, OPTIONS`
+    - Example: `nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, OPTIONS"`
+
+* `nginx.ingress.kubernetes.io/cors-allow-headers`: Controls which headers are accepted.
+
+    This is a multi-valued field, separated by ',' and accepts letters, numbers, _ and -.
+
+    - Default: `DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization`
+    - Example: `nginx.ingress.kubernetes.io/cors-allow-headers: "X-Forwarded-For, X-app123-XPTO"`
+
+* `nginx.ingress.kubernetes.io/cors-expose-headers`: Controls which headers are exposed to response.
+
+    This is a multi-valued field, separated by ',' and accepts letters, numbers, _, - and *.
+
+    - Default: *empty*
+    - Example: `nginx.ingress.kubernetes.io/cors-expose-headers: "*, X-CustomResponseHeader"`
+
+* `nginx.ingress.kubernetes.io/cors-allow-origin`: Controls what's the accepted Origin for CORS.
+
+    This is a single field value, with the following format: `http(s)://origin-site.com` or `http(s)://origin-site.com:port`
+
+    - Default: `*`
+    - Example: `nginx.ingress.kubernetes.io/cors-allow-origin: "https://origin-site.com:4443"`
+
+* `nginx.ingress.kubernetes.io/cors-allow-credentials`: Controls if credentials can be passed during CORS operations.
+
+    - Default: `true`
+    - Example: `nginx.ingress.kubernetes.io/cors-allow-credentials: "false"`
+
+* `nginx.ingress.kubernetes.io/cors-max-age`: Controls how long preflight requests can be cached.
+
+    - Default: `1728000`
+    - Example: `nginx.ingress.kubernetes.io/cors-max-age: 600`
 
 !!! note
     For more information please see [https://enable-cors.org](https://enable-cors.org/server_nginx.html)
@@ -532,7 +531,7 @@ to include that in access logs.
 1. The annotations below creates Global Rate Limiting instance per ingress.
 That means if there are multuple paths configured under the same ingress,
 the Global Rate Limiting will count requests to all the paths under the same counter.
-Extract a path out into its own ingres if you need to isolate a certain path.
+Extract a path out into its own ingress if you need to isolate a certain path.
 
 
 * `nginx.ingress.kubernetes.io/global-rate-limit`: Configures maximum allowed number of requests per window. Required.
diff --git a/docs/user-guide/nginx-configuration/configmap.md b/docs/user-guide/nginx-configuration/configmap.md
index 6e6152c5f..c8aa27373 100755
--- a/docs/user-guide/nginx-configuration/configmap.md
+++ b/docs/user-guide/nginx-configuration/configmap.md
@@ -203,6 +203,7 @@ The following table shows a configuration option's name, type, and the default v
 |[global-rate-limit-memcached-max-idle-timeout](#global-rate-limit)|int|10000|
 |[global-rate-limit-memcached-pool-size](#global-rate-limit)|int|50|
 |[global-rate-limit-status-code](#global-rate-limit)|int|429|
+|[service-upstream](#service-upstream)|bool|"false"|
 
 ## add-headers
 
@@ -1224,3 +1225,8 @@ Configure `memcached` client for [Global Rate Limiting](https://github.com/kuber
 
 These settings get used by [lua-resty-global-throttle](https://github.com/ElvinEfendi/lua-resty-global-throttle)
 that ingress-nginx includes. Refer to the link to learn more about `lua-resty-global-throttle`.
+
+## service-upstream
+
+Set if the service's Cluster IP and port should be used instead of a list of all endpoints. This can be overwritten by an annotation on an Ingress rule.
+_**default:**_ "false"
diff --git a/images/custom-error-pages/Makefile b/images/custom-error-pages/Makefile
index d08fa27e1..7564b813d 100644
--- a/images/custom-error-pages/Makefile
+++ b/images/custom-error-pages/Makefile
@@ -1,105 +1,57 @@
-all: all-container
-
-BUILDTAGS=
-
-# Use the 0.0 tag for testing, it shouldn't clobber any release builds
-TAG?=0.4
-REGISTRY?=quay.io/kubernetes-ingress-controller
-GOOS?=linux
-DOCKER?=docker
-SED_I?=sed -i
-GOHOSTOS ?= $(shell go env GOHOSTOS)
-
-PKG=k8s.io/ingress-nginx/images/custom-error-pages
-
-ifeq ($(GOHOSTOS),darwin)
-  SED_I=sed -i ''
-endif
-
-REPO_INFO=$(shell git config --get remote.origin.url)
-
-ifndef COMMIT
-  COMMIT := git-$(shell git rev-parse --short HEAD)
-endif
-
-ARCH ?= $(shell go env GOARCH)
-GOARCH = ${ARCH}
-
-BASEIMAGE?=alpine:3.10
-
-ALL_ARCH = amd64 arm arm64
-
-QEMUVERSION=v4.1.0-1
-
-IMGNAME = custom-error-pages
-IMAGE = $(REGISTRY)/$(IMGNAME)
-MULTI_ARCH_IMG = $(IMAGE)-$(ARCH)
-
-ifeq ($(ARCH),arm)
-    QEMUARCH=arm
-	GOARCH=arm
-endif
-ifeq ($(ARCH),arm64)
-	QEMUARCH=aarch64
-endif
-
-TEMP_DIR := $(shell mktemp -d)
-
-DOCKERFILE := $(TEMP_DIR)/rootfs/Dockerfile
-
-sub-container-%:
-	$(MAKE) ARCH=$* build container
-
-sub-push-%:
-	$(MAKE) ARCH=$* push
-
-all-container: $(addprefix sub-container-,$(ALL_ARCH))
-
-all-push: $(addprefix sub-push-,$(ALL_ARCH))
-
-container: .container-$(ARCH)
-.container-$(ARCH):
-	cp -r ./* $(TEMP_DIR)
-	$(SED_I) 's|BASEIMAGE|$(BASEIMAGE)|g' $(DOCKERFILE)
-	$(SED_I) "s|QEMUARCH|$(QEMUARCH)|g" $(DOCKERFILE)
-
-ifeq ($(ARCH),amd64)
-	# When building "normally" for amd64, remove the whole line, it has no part in the amd64 image
-	$(SED_I) "/CROSS_BUILD_/d" $(DOCKERFILE)
+# Copyright 2021 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.
+
+# Container image for nginx-errors.
+
+# 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
+
+TAG ?=v$(shell date +%m%d%Y)-$(shell git rev-parse --short HEAD)
+REGISTRY ?= local
+
+IMAGE = $(REGISTRY)/nginx-errors
+
+# required to enable buildx
+export DOCKER_CLI_EXPERIMENTAL=enabled
+
+# build with buildx
+PLATFORMS?=linux/amd64
+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
-	# When cross-building, only the placeholder "CROSS_BUILD_" should be removed
-	# Register /usr/bin/qemu-ARCH-static as the handler for ARM binaries in the kernel
-	# $(DOCKER) run --rm --privileged multiarch/qemu-user-static:register --reset
-	curl -sSL https://github.com/multiarch/qemu-user-static/releases/download/$(QEMUVERSION)/x86_64_qemu-$(QEMUARCH)-static.tar.gz | tar -xz -C $(TEMP_DIR)/rootfs
-	$(SED_I) "s/CROSS_BUILD_//g" $(DOCKERFILE)
+	@exec $(INIT_BUILDX)
 endif
+	@echo "done"
 
-	$(DOCKER) build -t $(MULTI_ARCH_IMG):$(TAG) $(TEMP_DIR)/rootfs
-
-ifeq ($(ARCH), amd64)
-	# This is for to maintain the backward compatibility
-	$(DOCKER) tag $(MULTI_ARCH_IMG):$(TAG) $(IMAGE):$(TAG)
-endif
-
-push: .push-$(ARCH)
-.push-$(ARCH):
-	$(DOCKER) push $(MULTI_ARCH_IMG):$(TAG)
-ifeq ($(ARCH), amd64)
-	$(DOCKER) push $(IMAGE):$(TAG)
-endif
-
-clean:
-	$(DOCKER) rmi -f $(MULTI_ARCH_IMG):$(TAG) || true
-
-build: clean
-	CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build -a -installsuffix cgo \
-		-ldflags "-s -w" \
-		-o ${TEMP_DIR}/rootfs/custom-error-pages ${PKG}/...
-
-release: all-container all-push
-	echo "done"
-
-.PHONY: register-qemu
-register-qemu:
-	# Register /usr/bin/qemu-ARCH-static as the handler for binaries in multiple platforms
-	$(DOCKER) run --rm --privileged multiarch/qemu-user-static:register --reset
+.PHONY: build push ensure-buildx
diff --git a/images/custom-error-pages/cloudbuild.yaml b/images/custom-error-pages/cloudbuild.yaml
new file mode 100644
index 000000000..9b913b3e6
--- /dev/null
+++ b/images/custom-error-pages/cloudbuild.yaml
@@ -0,0 +1,22 @@
+timeout: 1800s
+options:
+  substitution_option: ALLOW_LOOSE
+steps:
+  - name: gcr.io/k8s-testimages/gcb-docker-gcloud:v20201130-750d12f
+    entrypoint: bash
+    env:
+      - DOCKER_CLI_EXPERIMENTAL=enabled
+      - TAG=$_GIT_TAG
+      - 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 \
+      && make push
+substitutions:
+  _GIT_TAG: "12345"
+  _PULL_BASE_REF: "master"
diff --git a/images/custom-error-pages/rootfs/Dockerfile b/images/custom-error-pages/rootfs/Dockerfile
index aaf60efb1..fc09e92d1 100755
--- a/images/custom-error-pages/rootfs/Dockerfile
+++ b/images/custom-error-pages/rootfs/Dockerfile
@@ -1,4 +1,4 @@
-# Copyright 2017 The Kubernetes Authors. All rights reserved.
+# Copyright 2021 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.
@@ -12,10 +12,23 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-FROM BASEIMAGE
+FROM golang:1.17-alpine as builder
+RUN apk add git
 
-CROSS_BUILD_COPY qemu-QEMUARCH-static /usr/bin/
+WORKDIR /go/src/k8s.io/ingress-nginx/images/custom-error-pages
 
-COPY . /
+COPY . .
 
-CMD ["/custom-error-pages"]
+RUN go get . && \
+    CGO_ENABLED=0 go build -a -installsuffix cgo \
+	-ldflags "-s -w" \
+	-o nginx-errors .
+
+# 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/custom-error-pages/nginx-errors /
+USER nonroot:nonroot
+
+CMD ["/nginx-errors"]
diff --git a/images/custom-error-pages/rootfs/go.mod b/images/custom-error-pages/rootfs/go.mod
new file mode 100644
index 000000000..3040c5791
--- /dev/null
+++ b/images/custom-error-pages/rootfs/go.mod
@@ -0,0 +1,17 @@
+module k8s.io/ingress-nginx/custom-error-pages
+
+go 1.17
+
+require github.com/prometheus/client_golang v1.11.0
+
+require (
+	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/cespare/xxhash/v2 v2.1.1 // indirect
+	github.com/golang/protobuf v1.4.3 // indirect
+	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
+	github.com/prometheus/client_model v0.2.0 // indirect
+	github.com/prometheus/common v0.26.0 // indirect
+	github.com/prometheus/procfs v0.6.0 // indirect
+	golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 // indirect
+	google.golang.org/protobuf v1.26.0-rc.1 // indirect
+)
diff --git a/images/custom-error-pages/rootfs/go.sum b/images/custom-error-pages/rootfs/go.sum
new file mode 100644
index 000000000..6a42e5c54
--- /dev/null
+++ b/images/custom-error-pages/rootfs/go.sum
@@ -0,0 +1,138 @@
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
+github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
+github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
+github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q=
+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.26.0-rc.1 h1:7QnIQpGRHE5RnLKnESfDoxm2dTapTZua5a0kS0A+VXQ=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/images/custom-error-pages/main.go b/images/custom-error-pages/rootfs/main.go
similarity index 100%
rename from images/custom-error-pages/main.go
rename to images/custom-error-pages/rootfs/main.go
diff --git a/images/custom-error-pages/metrics.go b/images/custom-error-pages/rootfs/metrics.go
similarity index 100%
rename from images/custom-error-pages/metrics.go
rename to images/custom-error-pages/rootfs/metrics.go
diff --git a/images/echo/Makefile b/images/echo/Makefile
index ccfa711b0..f87aa1c76 100644
--- a/images/echo/Makefile
+++ b/images/echo/Makefile
@@ -36,7 +36,7 @@ build: ensure-buildx
 		--platform=${PLATFORMS} $(OUTPUT) \
 		--progress=$(PROGRESS) \
 		--pull \
-		--build-arg BASE_IMAGE=k8s.gcr.io/ingress-nginx/nginx:v20210904-gb7c973dce@sha256:fac972a7e43b18408ecb9e87da868df519428294e2e988c16be72479ee873c0e \
+		--build-arg BASE_IMAGE=k8s.gcr.io/ingress-nginx/nginx:v20210915-g498892514@sha256:8c1e48123e64e3f2b90ed32a53babd9b5f5431dad26beecdcb8fc185ded3b6dd \
 		--build-arg LUAROCKS_VERSION=3.3.1 \
 		--build-arg LUAROCKS_SHA=837481e408f7c06b59befe7ec194537c657687d624894bca7f79034302141a34 \
 		-t $(IMAGE):$(TAG) rootfs
diff --git a/images/kube-webhook-certgen/rootfs/README.md b/images/kube-webhook-certgen/rootfs/README.md
old mode 100755
new mode 100644
index 47a8c74c7..9f5781660
--- a/images/kube-webhook-certgen/rootfs/README.md
+++ b/images/kube-webhook-certgen/rootfs/README.md
@@ -7,14 +7,14 @@ creators.
 
 ## Overview
 Generates a CA and leaf certificate with a long (100y) expiration, then patches [Kubernetes Admission Webhooks](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/)
-by setting the `caBundle` field with the generated CA. 
+by setting the `caBundle` field with the generated CA.
 Can optionally patch the hooks `failurePolicy` setting - useful in cases where a single Helm chart needs to provision resources
 and hooks at the same time as patching.
 
 The utility works in two parts, optimized to work better with the Helm provisioning process that leverages pre-install and post-install hooks to execute this as a Kubernetes job.
 
 ## Security Considerations
-This tool may not be adequate in all security environments. If a more complete solution is required, you may want to 
+This tool may not be adequate in all security environments. If a more complete solution is required, you may want to
 seek alternatives such as [jetstack/cert-manager](https://github.com/jetstack/cert-manager)
 
 ## Command line options
diff --git a/images/kube-webhook-certgen/rootfs/cmd/create.go b/images/kube-webhook-certgen/rootfs/cmd/create.go
old mode 100755
new mode 100644
index 799d300ca..576a59bc0
--- a/images/kube-webhook-certgen/rootfs/cmd/create.go
+++ b/images/kube-webhook-certgen/rootfs/cmd/create.go
@@ -7,17 +7,16 @@ import (
 	"github.com/spf13/cobra"
 )
 
-var (
-	create = &cobra.Command{
-		Use:    "create",
-		Short:  "Generate a ca and server cert+key and store the results in a secret 'secret-name' in 'namespace'",
-		Long:   "Generate a ca and server cert+key and store the results in a secret 'secret-name' in 'namespace'",
-		PreRun: configureLogging,
-		Run:    createCommand}
-)
+var create = &cobra.Command{
+	Use:    "create",
+	Short:  "Generate a ca and server cert+key and store the results in a secret 'secret-name' in 'namespace'",
+	Long:   "Generate a ca and server cert+key and store the results in a secret 'secret-name' in 'namespace'",
+	PreRun: configureLogging,
+	Run:    createCommand,
+}
 
 func createCommand(cmd *cobra.Command, args []string) {
-	k := k8s.New(cfg.kubeconfig)
+	k := k8s.New(newKubernetesClient(cfg.kubeconfig))
 	ca := k.GetCaFromSecret(cfg.secretName, cfg.namespace)
 	if ca == nil {
 		log.Info("creating new secret")
diff --git a/images/kube-webhook-certgen/rootfs/cmd/patch.go b/images/kube-webhook-certgen/rootfs/cmd/patch.go
old mode 100755
new mode 100644
index b74eb1f3a..d139b37c0
--- a/images/kube-webhook-certgen/rootfs/cmd/patch.go
+++ b/images/kube-webhook-certgen/rootfs/cmd/patch.go
@@ -1,28 +1,24 @@
 package cmd
 
 import (
-	"os"
-
 	"github.com/jet/kube-webhook-certgen/pkg/k8s"
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 	admissionv1 "k8s.io/api/admissionregistration/v1"
 )
 
-var (
-	patch = &cobra.Command{
-		Use:    "patch",
-		Short:  "Patch a validatingwebhookconfiguration and mutatingwebhookconfiguration 'webhook-name' by using the ca from 'secret-name' in 'namespace'",
-		Long:   "Patch a validatingwebhookconfiguration and mutatingwebhookconfiguration 'webhook-name' by using the ca from 'secret-name' in 'namespace'",
-		PreRun: prePatchCommand,
-		Run:    patchCommand}
-)
+var patch = &cobra.Command{
+	Use:    "patch",
+	Short:  "Patch a validatingwebhookconfiguration and mutatingwebhookconfiguration 'webhook-name' by using the ca from 'secret-name' in 'namespace'",
+	Long:   "Patch a validatingwebhookconfiguration and mutatingwebhookconfiguration 'webhook-name' by using the ca from 'secret-name' in 'namespace'",
+	PreRun: prePatchCommand,
+	Run:    patchCommand,
+}
 
 func prePatchCommand(cmd *cobra.Command, args []string) {
 	configureLogging(cmd, args)
-	if cfg.patchMutating == false && cfg.patchValidating == false {
+	if !cfg.patchMutating && !cfg.patchValidating {
 		log.Fatal("patch-validating=false, patch-mutating=false. You must patch at least one kind of webhook, otherwise this command is a no-op")
-		os.Exit(1)
 	}
 	switch cfg.patchFailurePolicy {
 	case "":
@@ -33,12 +29,11 @@ func prePatchCommand(cmd *cobra.Command, args []string) {
 		break
 	default:
 		log.Fatalf("patch-failure-policy %s is not valid", cfg.patchFailurePolicy)
-		os.Exit(1)
 	}
 }
 
 func patchCommand(_ *cobra.Command, _ []string) {
-	k := k8s.New(cfg.kubeconfig)
+	k := k8s.New(newKubernetesClient(cfg.kubeconfig))
 	ca := k.GetCaFromSecret(cfg.secretName, cfg.namespace)
 
 	if ca == nil {
diff --git a/images/kube-webhook-certgen/rootfs/cmd/root.go b/images/kube-webhook-certgen/rootfs/cmd/root.go
old mode 100755
new mode 100644
index bee41c26c..03f87ac06
--- a/images/kube-webhook-certgen/rootfs/cmd/root.go
+++ b/images/kube-webhook-certgen/rootfs/cmd/root.go
@@ -7,6 +7,8 @@ import (
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 	admissionv1 "k8s.io/api/admissionregistration/v1"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/tools/clientcmd"
 )
 
 var (
@@ -81,3 +83,17 @@ func getFormatter(logfmt string) log.Formatter {
 	log.Fatalf("invalid log format '%s'", logfmt)
 	return nil
 }
+
+func newKubernetesClient(kubeconfig string) kubernetes.Interface {
+	config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
+	if err != nil {
+		log.WithError(err).Fatal("error building kubernetes config")
+	}
+
+	c, err := kubernetes.NewForConfig(config)
+	if err != nil {
+		log.WithError(err).Fatal("error creating kubernetes client")
+	}
+
+	return c
+}
diff --git a/images/kube-webhook-certgen/rootfs/pkg/certs/certs.go b/images/kube-webhook-certgen/rootfs/pkg/certs/certs.go
index 3e60f3311..ea6f4b27c 100644
--- a/images/kube-webhook-certgen/rootfs/pkg/certs/certs.go
+++ b/images/kube-webhook-certgen/rootfs/pkg/certs/certs.go
@@ -7,11 +7,12 @@ import (
 	"crypto/x509"
 	"crypto/x509/pkix"
 	"encoding/pem"
-	log "github.com/sirupsen/logrus"
 	"math/big"
 	"net"
 	"strings"
 	"time"
+
+	log "github.com/sirupsen/logrus"
 )
 
 // GenerateCerts venerates a ca with a leaf certificate and key and returns the ca, cert and key as PEM encoded slices
diff --git a/images/kube-webhook-certgen/rootfs/pkg/k8s/k8s.go b/images/kube-webhook-certgen/rootfs/pkg/k8s/k8s.go
old mode 100755
new mode 100644
index f0f21abd8..2d2000a8a
--- a/images/kube-webhook-certgen/rootfs/pkg/k8s/k8s.go
+++ b/images/kube-webhook-certgen/rootfs/pkg/k8s/k8s.go
@@ -9,92 +9,96 @@ import (
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
-	"k8s.io/client-go/tools/clientcmd"
 )
 
 type k8s struct {
 	clientset kubernetes.Interface
 }
 
-func New(kubeconfig string) *k8s {
-	config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
-	if err != nil {
-		log.WithError(err).Fatal("error building kubernetes config")
+func New(clientset kubernetes.Interface) *k8s {
+	if clientset == nil {
+		log.Fatal("no kubernetes client given")
 	}
 
-	c, err := kubernetes.NewForConfig(config)
-	if err != nil {
-		log.WithError(err).Fatal("error creating kubernetes client")
+	return &k8s{
+		clientset: clientset,
 	}
-
-	return &k8s{clientset: c}
 }
 
 // PatchWebhookConfigurations will patch validatingWebhook and mutatingWebhook clientConfig configurations with
 // the provided ca data. If failurePolicy is provided, patch all webhooks with this value
 func (k8s *k8s) PatchWebhookConfigurations(
-	configurationNames string, ca []byte,
+	configurationName string,
+	ca []byte,
 	failurePolicy *admissionv1.FailurePolicyType,
-	patchMutating bool, patchValidating bool) {
-
-	log.Infof("patching webhook configurations '%s' mutating=%t, validating=%t, failurePolicy=%s", configurationNames, patchMutating, patchValidating, *failurePolicy)
+	patchMutating bool,
+	patchValidating bool,
+) {
+	log.Infof("patching webhook configurations '%s' mutating=%t, validating=%t, failurePolicy=%s", configurationName, patchMutating, patchValidating, *failurePolicy)
 
 	if patchValidating {
-		valHook, err := k8s.clientset.
-			AdmissionregistrationV1().
-			ValidatingWebhookConfigurations().
-			Get(context.TODO(), configurationNames, metav1.GetOptions{})
-
-		if err != nil {
-			log.WithField("err", err).Fatal("failed getting validating webhook")
-		}
-
-		for i := range valHook.Webhooks {
-			h := &valHook.Webhooks[i]
-			h.ClientConfig.CABundle = ca
-			if *failurePolicy != "" {
-				h.FailurePolicy = failurePolicy
-			}
-		}
-
-		if _, err = k8s.clientset.AdmissionregistrationV1().
-			ValidatingWebhookConfigurations().
-			Update(context.TODO(), valHook, metav1.UpdateOptions{}); err != nil {
-			log.WithField("err", err).Fatal("failed patching validating webhook")
-		}
-		log.Debug("patched validating hook")
+		k8s.patchValidating(configurationName, ca, failurePolicy)
 	} else {
 		log.Debug("validating hook patching not required")
 	}
 
 	if patchMutating {
-		mutHook, err := k8s.clientset.
-			AdmissionregistrationV1().
-			MutatingWebhookConfigurations().
-			Get(context.TODO(), configurationNames, metav1.GetOptions{})
-		if err != nil {
-			log.WithField("err", err).Fatal("failed getting validating webhook")
-		}
+		k8s.patchMutating(configurationName, ca, failurePolicy)
+	} else {
+		log.Debug("mutating hook patching not required")
+	}
+
+	log.Info("Patched hook(s)")
+}
+
+func (k8s *k8s) patchValidating(configurationName string, ca []byte, failurePolicy *admissionv1.FailurePolicyType) {
+	valHook, err := k8s.clientset.
+		AdmissionregistrationV1().
+		ValidatingWebhookConfigurations().
+		Get(context.TODO(), configurationName, metav1.GetOptions{})
+	if err != nil {
+		log.WithField("err", err).Fatal("failed getting validating webhook")
+	}
 
-		for i := range mutHook.Webhooks {
-			h := &mutHook.Webhooks[i]
-			h.ClientConfig.CABundle = ca
-			if *failurePolicy != "" {
-				h.FailurePolicy = failurePolicy
-			}
+	for i := range valHook.Webhooks {
+		h := &valHook.Webhooks[i]
+		h.ClientConfig.CABundle = ca
+		if *failurePolicy != "" {
+			h.FailurePolicy = failurePolicy
 		}
+	}
+
+	if _, err = k8s.clientset.AdmissionregistrationV1().
+		ValidatingWebhookConfigurations().
+		Update(context.TODO(), valHook, metav1.UpdateOptions{}); err != nil {
+		log.WithField("err", err).Fatal("failed patching validating webhook")
+	}
+	log.Debug("patched validating hook")
+}
 
-		if _, err = k8s.clientset.AdmissionregistrationV1().
-			MutatingWebhookConfigurations().
-			Update(context.TODO(), mutHook, metav1.UpdateOptions{}); err != nil {
-			log.WithField("err", err).Fatal("failed patching validating webhook")
+func (k8s *k8s) patchMutating(configurationName string, ca []byte, failurePolicy *admissionv1.FailurePolicyType) {
+	mutHook, err := k8s.clientset.
+		AdmissionregistrationV1().
+		MutatingWebhookConfigurations().
+		Get(context.TODO(), configurationName, metav1.GetOptions{})
+	if err != nil {
+		log.WithField("err", err).Fatal("failed getting mutating webhook")
+	}
+
+	for i := range mutHook.Webhooks {
+		h := &mutHook.Webhooks[i]
+		h.ClientConfig.CABundle = ca
+		if *failurePolicy != "" {
+			h.FailurePolicy = failurePolicy
 		}
-		log.Debug("patched mutating hook")
-	} else {
-		log.Debug("mutating hook patching not required")
 	}
 
-	log.Info("Patched hook(s)")
+	if _, err = k8s.clientset.AdmissionregistrationV1().
+		MutatingWebhookConfigurations().
+		Update(context.TODO(), mutHook, metav1.UpdateOptions{}); err != nil {
+		log.WithField("err", err).Fatal("failed patching mutating webhook")
+	}
+	log.Debug("patched mutating hook")
 }
 
 // GetCaFromSecret will check for the presence of a secret. If it exists, will return the content of the
@@ -120,7 +124,6 @@ func (k8s *k8s) GetCaFromSecret(secretName string, namespace string) []byte {
 
 // SaveCertsToSecret saves the provided ca, cert and key into a secret in the specified namespace.
 func (k8s *k8s) SaveCertsToSecret(secretName, namespace, certName, keyName string, ca, cert, key []byte) {
-
 	log.Debugf("saving to secret '%s' in namespace '%s'", secretName, namespace)
 	secret := &v1.Secret{
 		ObjectMeta: metav1.ObjectMeta{
diff --git a/images/nginx/README.md b/images/nginx/README.md
index fc247699e..947e97db9 100644
--- a/images/nginx/README.md
+++ b/images/nginx/README.md
@@ -18,6 +18,6 @@ This image provides a default configuration file with no backend servers.
 _Using docker_
 
 ```console
-docker run -v /some/nginx.con:/etc/nginx/nginx.conf:ro k8s.gcr.io/ingress-nginx/nginx:v20210904-gb7c973dce@sha256:fac972a7e43b18408ecb9e87da868df519428294e2e988c16be72479ee873c0e
+docker run -v /some/nginx.con:/etc/nginx/nginx.conf:ro k8s.gcr.io/ingress-nginx/nginx:v20210915-g498892514@sha256:8c1e48123e64e3f2b90ed32a53babd9b5f5431dad26beecdcb8fc185ded3b6dd
 ```
 
diff --git a/images/nginx/rc.yaml b/images/nginx/rc.yaml
index e5654260f..e8cef4e35 100644
--- a/images/nginx/rc.yaml
+++ b/images/nginx/rc.yaml
@@ -38,7 +38,7 @@ spec:
     spec:
       containers:
         - name: nginx
-          image: k8s.gcr.io/ingress-nginx/nginx:v20210904-gb7c973dce@sha256:fac972a7e43b18408ecb9e87da868df519428294e2e988c16be72479ee873c0e
+          image: k8s.gcr.io/ingress-nginx/nginx:v20210915-g498892514@sha256:8c1e48123e64e3f2b90ed32a53babd9b5f5431dad26beecdcb8fc185ded3b6dd
           ports:
             - containerPort: 80
             - containerPort: 443
diff --git a/images/nginx/rootfs/build.sh b/images/nginx/rootfs/build.sh
index d8d57bafe..46daf5d5f 100755
--- a/images/nginx/rootfs/build.sh
+++ b/images/nginx/rootfs/build.sh
@@ -18,7 +18,7 @@ set -o errexit
 set -o nounset
 set -o pipefail
 
-export NGINX_VERSION=1.20.1
+export NGINX_VERSION=1.19.9
 
 # Check for recent changes: https://github.com/vision5/ngx_devel_kit/compare/v0.3.1...master
 export NDK_VERSION=0.3.1
@@ -197,7 +197,7 @@ mkdir --verbose -p "$BUILD_PATH"
 cd "$BUILD_PATH"
 
 # download, verify and extract the source files
-get_src e462e11533d5c30baa05df7652160ff5979591d291736cfa5edb9fd2edb48c49 \
+get_src 2e35dff06a9826e8aca940e9e8be46b7e4b12c19a48d55bfc2dc28fc9cc7d841 \
         "https://nginx.org/download/nginx-$NGINX_VERSION.tar.gz"
 
 get_src 0e971105e210d272a497567fa2e2c256f4e39b845a5ba80d373e26ba1abfbd85 \
@@ -568,7 +568,11 @@ cd "$BUILD_PATH/nginx-$NGINX_VERSION"
 # apply nginx patches
 for PATCH in `ls /patches`;do
   echo "Patch: $PATCH"
-  patch -p1 < /patches/$PATCH
+  if [[ "$PATCH" == *.txt ]]; then
+    patch -p0 < /patches/$PATCH
+  else
+    patch -p1 < /patches/$PATCH
+  fi
 done
 
 WITH_FLAGS="--with-debug \
diff --git a/images/nginx/rootfs/patches/nginx-1.19.3-no_Werror.patch b/images/nginx/rootfs/patches/nginx-1.19.3-no_Werror.patch
deleted file mode 100644
index 2cf7cd9c7..000000000
--- a/images/nginx/rootfs/patches/nginx-1.19.3-no_Werror.patch
+++ /dev/null
@@ -1,36 +0,0 @@
-diff -urp nginx-1.19.3/auto/cc/clang nginx-1.19.3-patched/auto/cc/clang
---- nginx-1.19.3/auto/cc/clang	2014-03-04 03:39:24.000000000 -0800
-+++ nginx-1.19.3-patched/auto/cc/clang	2014-03-13 20:54:26.241413360 -0700
-@@ -89,7 +89,7 @@ CFLAGS="$CFLAGS -Wconditional-uninitiali
- CFLAGS="$CFLAGS -Wno-unused-parameter"
- 
- # stop on warning
--CFLAGS="$CFLAGS -Werror"
-+#CFLAGS="$CFLAGS -Werror"
- 
- # debug
- CFLAGS="$CFLAGS -g"
-diff -urp nginx-1.19.3/auto/cc/gcc nginx-1.19.3-patched/auto/cc/gcc
---- nginx-1.19.3/auto/cc/gcc	2014-03-04 03:39:24.000000000 -0800
-+++ nginx-1.19.3-patched/auto/cc/gcc	2014-03-13 20:54:13.301355329 -0700
-@@ -168,7 +168,7 @@ esac
- 
- 
- # stop on warning
--CFLAGS="$CFLAGS -Werror"
-+#CFLAGS="$CFLAGS -Werror"
- 
- # debug
- CFLAGS="$CFLAGS -g"
-diff -urp nginx-1.19.3/auto/cc/icc nginx-1.19.3-patched/auto/cc/icc
---- nginx-1.19.3/auto/cc/icc	2014-03-04 03:39:24.000000000 -0800
-+++ nginx-1.19.3-patched/auto/cc/icc	2014-03-13 20:54:13.301355329 -0700
-@@ -115,7 +115,7 @@ case "$NGX_ICC_VER" in
- esac
- 
- # stop on warning
--CFLAGS="$CFLAGS -Werror"
-+#CFLAGS="$CFLAGS -Werror"
- 
- # debug
- CFLAGS="$CFLAGS -g"
diff --git a/images/nginx/rootfs/patches/nginx-1.19.3-balancer_status_code.patch b/images/nginx/rootfs/patches/nginx-1.19.9-balancer_status_code.patch
similarity index 100%
rename from images/nginx/rootfs/patches/nginx-1.19.3-balancer_status_code.patch
rename to images/nginx/rootfs/patches/nginx-1.19.9-balancer_status_code.patch
diff --git a/images/nginx/rootfs/patches/nginx-1.19.3-cache_manager_exit.patch b/images/nginx/rootfs/patches/nginx-1.19.9-cache_manager_exit.patch
similarity index 96%
rename from images/nginx/rootfs/patches/nginx-1.19.3-cache_manager_exit.patch
rename to images/nginx/rootfs/patches/nginx-1.19.9-cache_manager_exit.patch
index f1f81da2c..91ee63a26 100644
--- a/images/nginx/rootfs/patches/nginx-1.19.3-cache_manager_exit.patch
+++ b/images/nginx/rootfs/patches/nginx-1.19.9-cache_manager_exit.patch
@@ -8,7 +8,7 @@ Cache: gracefully exit the cache manager process.
 diff -r dea321e5c021 -r f64218e1ac96 src/os/unix/ngx_process_cycle.c
 --- a/src/os/unix/ngx_process_cycle.c	Thu Oct 31 18:23:49 2013 +0400
 +++ b/src/os/unix/ngx_process_cycle.c	Mon Nov 04 12:48:50 2013 -0800
-@@ -1335,7 +1335,7 @@
+@@ -1134,7 +1134,7 @@
  
          if (ngx_terminate || ngx_quit) {
              ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
diff --git a/images/nginx/rootfs/patches/nginx-1.19.3-delayed_posted_events.patch b/images/nginx/rootfs/patches/nginx-1.19.9-delayed_posted_events.patch
similarity index 100%
rename from images/nginx/rootfs/patches/nginx-1.19.3-delayed_posted_events.patch
rename to images/nginx/rootfs/patches/nginx-1.19.9-delayed_posted_events.patch
diff --git a/images/nginx/rootfs/patches/nginx-1.19.3-hash_overflow.patch b/images/nginx/rootfs/patches/nginx-1.19.9-hash_overflow.patch
similarity index 100%
rename from images/nginx/rootfs/patches/nginx-1.19.3-hash_overflow.patch
rename to images/nginx/rootfs/patches/nginx-1.19.9-hash_overflow.patch
diff --git a/images/nginx/rootfs/patches/nginx-1.19.3-init_cycle_pool_release.patch b/images/nginx/rootfs/patches/nginx-1.19.9-init_cycle_pool_release.patch
similarity index 62%
rename from images/nginx/rootfs/patches/nginx-1.19.3-init_cycle_pool_release.patch
rename to images/nginx/rootfs/patches/nginx-1.19.9-init_cycle_pool_release.patch
index b6f46674f..aa2df4660 100644
--- a/images/nginx/rootfs/patches/nginx-1.19.3-init_cycle_pool_release.patch
+++ b/images/nginx/rootfs/patches/nginx-1.19.9-init_cycle_pool_release.patch
@@ -1,6 +1,6 @@
-diff -rup nginx-1.19.3/src/core/nginx.c nginx-1.19.3-patched/src/core/nginx.c
---- nginx-1.19.3/src/core/nginx.c	2017-12-17 00:00:38.136470108 -0800
-+++ nginx-1.19.3-patched/src/core/nginx.c	2017-12-16 23:59:51.680958322 -0800
+diff -rup nginx-1.19.9/src/core/nginx.c nginx-1.19.9-patched/src/core/nginx.c
+--- nginx-1.19.9/src/core/nginx.c	2017-12-17 00:00:38.136470108 -0800
++++ nginx-1.19.9-patched/src/core/nginx.c	2017-12-16 23:59:51.680958322 -0800
 @@ -186,6 +186,7 @@ static u_char      *ngx_prefix;
  static u_char      *ngx_conf_file;
  static u_char      *ngx_conf_params;
@@ -18,9 +18,9 @@ diff -rup nginx-1.19.3/src/core/nginx.c nginx-1.19.3-patched/src/core/nginx.c
      if (ngx_save_argv(&init_cycle, argc, argv) != NGX_OK) {
          return 1;
      }
-diff -rup nginx-1.19.3/src/core/ngx_core.h nginx-1.19.3-patched/src/core/ngx_core.h
---- nginx-1.19.3/src/core/ngx_core.h	2017-10-10 08:22:51.000000000 -0700
-+++ nginx-1.19.3-patched/src/core/ngx_core.h	2017-12-16 23:59:51.679958370 -0800
+diff -rup nginx-1.19.9/src/core/ngx_core.h nginx-1.19.9-patched/src/core/ngx_core.h
+--- nginx-1.19.9/src/core/ngx_core.h	2017-10-10 08:22:51.000000000 -0700
++++ nginx-1.19.9-patched/src/core/ngx_core.h	2017-12-16 23:59:51.679958370 -0800
 @@ -108,4 +108,6 @@ void ngx_cpuinfo(void);
  #define NGX_DISABLE_SYMLINKS_NOTOWNER   2
  #endif
@@ -28,9 +28,9 @@ diff -rup nginx-1.19.3/src/core/ngx_core.h nginx-1.19.3-patched/src/core/ngx_cor
 +extern ngx_pool_t        *saved_init_cycle_pool;
 +
  #endif /* _NGX_CORE_H_INCLUDED_ */
-diff -rup nginx-1.19.3/src/core/ngx_cycle.c nginx-1.19.3-patched/src/core/ngx_cycle.c
---- nginx-1.19.3/src/core/ngx_cycle.c	2017-10-10 08:22:51.000000000 -0700
-+++ nginx-1.19.3-patched/src/core/ngx_cycle.c	2017-12-16 23:59:51.678958419 -0800
+diff -rup nginx-1.19.9/src/core/ngx_cycle.c nginx-1.19.9-patched/src/core/ngx_cycle.c
+--- nginx-1.19.9/src/core/ngx_cycle.c	2017-10-10 08:22:51.000000000 -0700
++++ nginx-1.19.9-patched/src/core/ngx_cycle.c	2017-12-16 23:59:51.678958419 -0800
 @@ -748,6 +748,10 @@ old_shm_zone_done:
  
      if (ngx_process == NGX_PROCESS_MASTER || ngx_is_init_cycle(old_cycle)) {
@@ -42,10 +42,10 @@ diff -rup nginx-1.19.3/src/core/ngx_cycle.c nginx-1.19.3-patched/src/core/ngx_cy
          ngx_destroy_pool(old_cycle->pool);
          cycle->old_cycle = NULL;
  
-diff -rup nginx-1.19.3/src/os/unix/ngx_process_cycle.c nginx-1.19.3-patched/src/os/unix/ngx_process_cycle.c
---- nginx-1.19.3/src/os/unix/ngx_process_cycle.c	2017-12-17 00:00:38.142469762 -0800
-+++ nginx-1.19.3-patched/src/os/unix/ngx_process_cycle.c	2017-12-16 23:59:51.691957791 -0800
-@@ -783,6 +783,11 @@ ngx_master_process_exit(ngx_cycle_t *cyc
+diff -rup nginx-1.19.9/src/os/unix/ngx_process_cycle.c nginx-1.19.9-patched/src/os/unix/ngx_process_cycle.c
+--- nginx-1.19.9/src/os/unix/ngx_process_cycle.c	2017-12-17 00:00:38.142469762 -0800
++++ nginx-1.19.9-patched/src/os/unix/ngx_process_cycle.c	2017-12-16 23:59:51.691957791 -0800
+@@ -687,6 +692,11 @@ ngx_master_process_exit(ngx_cycle_t *cyc
      ngx_exit_cycle.files_n = ngx_cycle->files_n;
      ngx_cycle = &ngx_exit_cycle;
  
diff --git a/images/nginx/rootfs/patches/nginx-1.19.3-larger_max_error_str.patch b/images/nginx/rootfs/patches/nginx-1.19.9-larger_max_error_str.patch
similarity index 62%
rename from images/nginx/rootfs/patches/nginx-1.19.3-larger_max_error_str.patch
rename to images/nginx/rootfs/patches/nginx-1.19.9-larger_max_error_str.patch
index c7013e05d..0628d3abb 100644
--- a/images/nginx/rootfs/patches/nginx-1.19.3-larger_max_error_str.patch
+++ b/images/nginx/rootfs/patches/nginx-1.19.9-larger_max_error_str.patch
@@ -1,5 +1,5 @@
---- nginx-1.19.3/src/core/ngx_log.h	2013-10-08 05:07:14.000000000 -0700
-+++ nginx-1.19.3-patched/src/core/ngx_log.h	2013-12-05 20:35:35.996236720 -0800
+--- nginx-1.19.9/src/core/ngx_log.h	2013-10-08 05:07:14.000000000 -0700
++++ nginx-1.19.9-patched/src/core/ngx_log.h	2013-12-05 20:35:35.996236720 -0800
 @@ -64,7 +64,9 @@ struct ngx_log_s {
  };
  
diff --git a/images/nginx/rootfs/patches/nginx-1.19.9-no_Werror.patch b/images/nginx/rootfs/patches/nginx-1.19.9-no_Werror.patch
new file mode 100644
index 000000000..7bb0ac902
--- /dev/null
+++ b/images/nginx/rootfs/patches/nginx-1.19.9-no_Werror.patch
@@ -0,0 +1,36 @@
+diff -urp nginx-1.19.9/auto/cc/clang nginx-1.19.9-patched/auto/cc/clang
+--- nginx-1.19.9/auto/cc/clang	2014-03-04 03:39:24.000000000 -0800
++++ nginx-1.19.9-patched/auto/cc/clang	2014-03-13 20:54:26.241413360 -0700
+@@ -89,7 +89,7 @@ CFLAGS="$CFLAGS -Wconditional-uninitiali
+ CFLAGS="$CFLAGS -Wno-unused-parameter"
+ 
+ # stop on warning
+-CFLAGS="$CFLAGS -Werror"
++#CFLAGS="$CFLAGS -Werror"
+ 
+ # debug
+ CFLAGS="$CFLAGS -g"
+diff -urp nginx-1.19.9/auto/cc/gcc nginx-1.19.9-patched/auto/cc/gcc
+--- nginx-1.19.9/auto/cc/gcc	2014-03-04 03:39:24.000000000 -0800
++++ nginx-1.19.9-patched/auto/cc/gcc	2014-03-13 20:54:13.301355329 -0700
+@@ -168,7 +168,7 @@ esac
+ 
+ 
+ # stop on warning
+-CFLAGS="$CFLAGS -Werror"
++#CFLAGS="$CFLAGS -Werror"
+ 
+ # debug
+ CFLAGS="$CFLAGS -g"
+diff -urp nginx-1.19.9/auto/cc/icc nginx-1.19.9-patched/auto/cc/icc
+--- nginx-1.19.9/auto/cc/icc	2014-03-04 03:39:24.000000000 -0800
++++ nginx-1.19.9-patched/auto/cc/icc	2014-03-13 20:54:13.301355329 -0700
+@@ -115,7 +115,7 @@ case "$NGX_ICC_VER" in
+ esac
+ 
+ # stop on warning
+-CFLAGS="$CFLAGS -Werror"
++#CFLAGS="$CFLAGS -Werror"
+ 
+ # debug
+ CFLAGS="$CFLAGS -g"
diff --git a/images/nginx/rootfs/patches/nginx-1.19.3-proxy_host_port_vars.patch b/images/nginx/rootfs/patches/nginx-1.19.9-proxy_host_port_vars.patch
similarity index 87%
rename from images/nginx/rootfs/patches/nginx-1.19.3-proxy_host_port_vars.patch
rename to images/nginx/rootfs/patches/nginx-1.19.9-proxy_host_port_vars.patch
index 63eaf0ca7..25282bda3 100644
--- a/images/nginx/rootfs/patches/nginx-1.19.3-proxy_host_port_vars.patch
+++ b/images/nginx/rootfs/patches/nginx-1.19.9-proxy_host_port_vars.patch
@@ -1,5 +1,5 @@
---- nginx-1.19.3/src/http/modules/ngx_http_proxy_module.c	2017-07-16 14:02:51.000000000 +0800
-+++ nginx-1.19.3-patched/src/http/modules/ngx_http_proxy_module.c	2017-07-16 14:02:51.000000000 +0800
+--- nginx-1.19.9/src/http/modules/ngx_http_proxy_module.c	2017-07-16 14:02:51.000000000 +0800
++++ nginx-1.19.9-patched/src/http/modules/ngx_http_proxy_module.c	2017-07-16 14:02:51.000000000 +0800
 @@ -793,13 +793,13 @@ static ngx_keyval_t  ngx_http_proxy_cach
  static ngx_http_variable_t  ngx_http_proxy_vars[] = {
  
diff --git a/images/nginx/rootfs/patches/nginx-1.19.3-resolver_conf_parsing.patch b/images/nginx/rootfs/patches/nginx-1.19.9-resolver_conf_parsing.patch
similarity index 100%
rename from images/nginx/rootfs/patches/nginx-1.19.3-resolver_conf_parsing.patch
rename to images/nginx/rootfs/patches/nginx-1.19.9-resolver_conf_parsing.patch
diff --git a/images/nginx/rootfs/patches/nginx-1.19.3-reuseport_close_unused_fds.patch b/images/nginx/rootfs/patches/nginx-1.19.9-reuseport_close_unused_fds.patch
similarity index 100%
rename from images/nginx/rootfs/patches/nginx-1.19.3-reuseport_close_unused_fds.patch
rename to images/nginx/rootfs/patches/nginx-1.19.9-reuseport_close_unused_fds.patch
diff --git a/images/nginx/rootfs/patches/nginx-1.19.3-single_process_graceful_exit.patch b/images/nginx/rootfs/patches/nginx-1.19.9-single_process_graceful_exit.patch
similarity index 100%
rename from images/nginx/rootfs/patches/nginx-1.19.3-single_process_graceful_exit.patch
rename to images/nginx/rootfs/patches/nginx-1.19.9-single_process_graceful_exit.patch
diff --git a/images/nginx/rootfs/patches/nginx-1.19.3-socket_cloexec.patch b/images/nginx/rootfs/patches/nginx-1.19.9-socket_cloexec.patch
similarity index 98%
rename from images/nginx/rootfs/patches/nginx-1.19.3-socket_cloexec.patch
rename to images/nginx/rootfs/patches/nginx-1.19.9-socket_cloexec.patch
index 985ce573b..8ffe4c167 100644
--- a/images/nginx/rootfs/patches/nginx-1.19.3-socket_cloexec.patch
+++ b/images/nginx/rootfs/patches/nginx-1.19.9-socket_cloexec.patch
@@ -151,7 +151,7 @@ diff --git a/src/os/unix/ngx_process_cycle.c b/src/os/unix/ngx_process_cycle.c
 index c4376a5..48e8fa8 100644
 --- a/src/os/unix/ngx_process_cycle.c
 +++ b/src/os/unix/ngx_process_cycle.c
-@@ -1032,6 +1032,9 @@ ngx_worker_process_exit(ngx_cycle_t *cycle)
+@@ -960,6 +1029,9 @@ ngx_worker_process_exit(ngx_cycle_t *cycle)
          for (i = 0; i < cycle->connection_n; i++) {
              if (c[i].fd != -1
                  && c[i].read
diff --git a/images/nginx/rootfs/patches/nginx-1.19.3-ssl_cert_cb_yield.patch b/images/nginx/rootfs/patches/nginx-1.19.9-ssl_cert_cb_yield.patch
similarity index 100%
rename from images/nginx/rootfs/patches/nginx-1.19.3-ssl_cert_cb_yield.patch
rename to images/nginx/rootfs/patches/nginx-1.19.9-ssl_cert_cb_yield.patch
diff --git a/images/nginx/rootfs/patches/nginx-1.19.3-ssl_sess_cb_yield.patch b/images/nginx/rootfs/patches/nginx-1.19.9-ssl_sess_cb_yield.patch
similarity index 100%
rename from images/nginx/rootfs/patches/nginx-1.19.3-ssl_sess_cb_yield.patch
rename to images/nginx/rootfs/patches/nginx-1.19.9-ssl_sess_cb_yield.patch
diff --git a/images/nginx/rootfs/patches/nginx-1.19.3-stream_proxy_get_next_upstream_tries.patch b/images/nginx/rootfs/patches/nginx-1.19.9-stream_proxy_get_next_upstream_tries.patch
similarity index 100%
rename from images/nginx/rootfs/patches/nginx-1.19.3-stream_proxy_get_next_upstream_tries.patch
rename to images/nginx/rootfs/patches/nginx-1.19.9-stream_proxy_get_next_upstream_tries.patch
diff --git a/images/nginx/rootfs/patches/nginx-1.19.3-stream_ssl_preread_no_skip.patch b/images/nginx/rootfs/patches/nginx-1.19.9-stream_ssl_preread_no_skip.patch
similarity index 100%
rename from images/nginx/rootfs/patches/nginx-1.19.3-stream_ssl_preread_no_skip.patch
rename to images/nginx/rootfs/patches/nginx-1.19.9-stream_ssl_preread_no_skip.patch
diff --git a/images/nginx/rootfs/patches/nginx-1.19.3-upstream_pipelining.patch b/images/nginx/rootfs/patches/nginx-1.19.9-upstream_pipelining.patch
similarity index 100%
rename from images/nginx/rootfs/patches/nginx-1.19.3-upstream_pipelining.patch
rename to images/nginx/rootfs/patches/nginx-1.19.9-upstream_pipelining.patch
diff --git a/images/nginx/rootfs/patches/nginx-1.19.3-upstream_timeout_fields.patch b/images/nginx/rootfs/patches/nginx-1.19.9-upstream_timeout_fields.patch
similarity index 100%
rename from images/nginx/rootfs/patches/nginx-1.19.3-upstream_timeout_fields.patch
rename to images/nginx/rootfs/patches/nginx-1.19.9-upstream_timeout_fields.patch
diff --git a/images/nginx/rootfs/patches/patch.2021.resolver.txt b/images/nginx/rootfs/patches/patch.2021.resolver.txt
new file mode 100644
index 000000000..6c895e61c
--- /dev/null
+++ b/images/nginx/rootfs/patches/patch.2021.resolver.txt
@@ -0,0 +1,23 @@
+diff --git src/core/ngx_resolver.c src/core/ngx_resolver.c
+--- src/core/ngx_resolver.c
++++ src/core/ngx_resolver.c
+@@ -4008,15 +4008,15 @@ done:
+             n = *src++;
+ 
+         } else {
++            if (dst != name->data) {
++                *dst++ = '.';
++            }
++
+             ngx_strlow(dst, src, n);
+             dst += n;
+             src += n;
+ 
+             n = *src++;
+-
+-            if (n != 0) {
+-                *dst++ = '.';
+-            }
+         }
+ 
+         if (n == 0) {
diff --git a/internal/ingress/annotations/serviceupstream/main.go b/internal/ingress/annotations/serviceupstream/main.go
index 4c44356d6..4a4879682 100644
--- a/internal/ingress/annotations/serviceupstream/main.go
+++ b/internal/ingress/annotations/serviceupstream/main.go
@@ -20,6 +20,7 @@ import (
 	networking "k8s.io/api/networking/v1"
 
 	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
+	"k8s.io/ingress-nginx/internal/ingress/errors"
 	"k8s.io/ingress-nginx/internal/ingress/resolver"
 )
 
@@ -33,5 +34,13 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
 }
 
 func (s serviceUpstream) Parse(ing *networking.Ingress) (interface{}, error) {
-	return parser.GetBoolAnnotation("service-upstream", ing)
+	defBackend := s.r.GetDefaultBackend()
+
+	val, err := parser.GetBoolAnnotation("service-upstream", ing)
+	// A missing annotation is not a problem, just use the default
+	if err == errors.ErrMissingAnnotations {
+		return defBackend.ServiceUpstream, nil
+	}
+
+	return val, nil
 }
diff --git a/internal/ingress/controller/config/config.go b/internal/ingress/controller/config/config.go
index b96e26772..0546e9618 100644
--- a/internal/ingress/controller/config/config.go
+++ b/internal/ingress/controller/config/config.go
@@ -860,6 +860,7 @@ func NewDefault() Configuration {
 			ProxyBuffering:           "off",
 			ProxyHTTPVersion:         "1.1",
 			ProxyMaxTempFileSize:     "1024m",
+			ServiceUpstream:          false,
 		},
 		UpstreamKeepaliveConnections:           320,
 		UpstreamKeepaliveTimeout:               60,
diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go
index 1fd22079b..38357d491 100644
--- a/internal/ingress/controller/controller.go
+++ b/internal/ingress/controller/controller.go
@@ -106,6 +106,7 @@ type Configuration struct {
 	ValidationWebhook         string
 	ValidationWebhookCertPath string
 	ValidationWebhookKeyPath  string
+	DisableFullValidationTest bool
 
 	GlobalExternalAuth  *ngx_config.GlobalExternalAuth
 	MaxmindEditionFiles *[]string
@@ -274,6 +275,10 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
 		return err
 	}
 
+	if n.cfg.DisableFullValidationTest {
+		_, _, pcfg = n.getConfiguration(ings[len(ings)-1:])
+	}
+
 	content, err := n.generateTemplate(cfg, *pcfg)
 	if err != nil {
 		n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name)
diff --git a/internal/ingress/controller/endpoints.go b/internal/ingress/controller/endpoints.go
index bdddcb0a0..26d7f298e 100644
--- a/internal/ingress/controller/endpoints.go
+++ b/internal/ingress/controller/endpoints.go
@@ -23,6 +23,7 @@ import (
 	"strconv"
 	"strings"
 
+	"k8s.io/apimachinery/pkg/util/intstr"
 	"k8s.io/apimachinery/pkg/util/validation"
 	"k8s.io/klog/v2"
 
@@ -81,7 +82,8 @@ func getEndpoints(s *corev1.Service, port *corev1.ServicePort, proto corev1.Prot
 	}
 
 	for _, ss := range ep.Subsets {
-		for _, epPort := range ss.Ports {
+		matchedPortNameFound := false
+		for i, epPort := range ss.Ports {
 
 			if !reflect.DeepEqual(epPort.Protocol, proto) {
 				continue
@@ -92,8 +94,16 @@ func getEndpoints(s *corev1.Service, port *corev1.ServicePort, proto corev1.Prot
 			if port.Name == "" {
 				// port.Name is optional if there is only one port
 				targetPort = epPort.Port
+				matchedPortNameFound = true
 			} else if port.Name == epPort.Name {
 				targetPort = epPort.Port
+				matchedPortNameFound = true
+			}
+
+			if i == len(ss.Ports)-1 && !matchedPortNameFound && port.TargetPort.Type == intstr.Int {
+				// use service target port if it's a number and no port name matched
+				// https://github.com/kubernetes/ingress-nginx/issues/7390
+				targetPort = port.TargetPort.IntVal
 			}
 
 			if targetPort <= 0 {
diff --git a/internal/ingress/controller/ingressclass/ingressclass.go b/internal/ingress/controller/ingressclass/ingressclass.go
index 025a4e2a5..f13a2a05c 100644
--- a/internal/ingress/controller/ingressclass/ingressclass.go
+++ b/internal/ingress/controller/ingressclass/ingressclass.go
@@ -42,4 +42,8 @@ type IngressClassConfiguration struct {
 	// WatchWithoutClass defines if Controller should watch to Ingress Objects that does
 	// not contain an IngressClass configuration
 	WatchWithoutClass bool
+
+	//IngressClassByName defines if the Controller should watch for Ingress Classes by
+	// .metadata.name together with .spec.Controller
+	IngressClassByName bool
 }
diff --git a/internal/ingress/controller/store/store.go b/internal/ingress/controller/store/store.go
index f65098a21..d5f7ce63c 100644
--- a/internal/ingress/controller/store/store.go
+++ b/internal/ingress/controller/store/store.go
@@ -426,7 +426,12 @@ func New(
 	ingressClassEventHandler := cache.ResourceEventHandlerFuncs{
 		AddFunc: func(obj interface{}) {
 			ingressclass := obj.(*networkingv1.IngressClass)
-			if ingressclass.Spec.Controller != icConfig.Controller {
+			foundClassByName := false
+			if icConfig.IngressClassByName && ingressclass.Name == icConfig.AnnotationValue {
+				klog.InfoS("adding ingressclass as ingress-class-by-name is configured", "ingressclass", klog.KObj(ingressclass))
+				foundClassByName = true
+			}
+			if !foundClassByName && ingressclass.Spec.Controller != icConfig.Controller {
 				klog.InfoS("ignoring ingressclass as the spec.controller is not the same of this ingress", "ingressclass", klog.KObj(ingressclass))
 				return
 			}
@@ -660,6 +665,24 @@ func New(
 	}
 
 	serviceHandler := cache.ResourceEventHandlerFuncs{
+		AddFunc: func(obj interface{}) {
+			svc := obj.(*corev1.Service)
+			if svc.Spec.Type == corev1.ServiceTypeExternalName {
+				updateCh.In() <- Event{
+					Type: CreateEvent,
+					Obj:  obj,
+				}
+			}
+		},
+		DeleteFunc: func(obj interface{}) {
+			svc := obj.(*corev1.Service)
+			if svc.Spec.Type == corev1.ServiceTypeExternalName {
+				updateCh.In() <- Event{
+					Type: DeleteEvent,
+					Obj:  obj,
+				}
+			}
+		},
 		UpdateFunc: func(old, cur interface{}) {
 			oldSvc := old.(*corev1.Service)
 			curSvc := cur.(*corev1.Service)
diff --git a/internal/ingress/defaults/main.go b/internal/ingress/defaults/main.go
index 03926baa0..bc9734257 100644
--- a/internal/ingress/defaults/main.go
+++ b/internal/ingress/defaults/main.go
@@ -161,4 +161,8 @@ type Backend struct {
 	// Sets the maximum temp file size when proxy-buffers capacity is exceeded.
 	// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_max_temp_file_size
 	ProxyMaxTempFileSize string `json:"proxy-max-temp-file-size"`
+
+	// By default, the NGINX ingress controller uses a list of all endpoints (Pod IP/port) in the NGINX upstream configuration.
+	// It disables that behavior and instead uses a single upstream in NGINX, the service's Cluster IP and port.
+	ServiceUpstream bool `json:"service-upstream"`
 }
diff --git a/internal/ingress/status/status.go b/internal/ingress/status/status.go
index cabc6b3a2..7e2db9189 100644
--- a/internal/ingress/status/status.go
+++ b/internal/ingress/status/status.go
@@ -26,7 +26,6 @@ import (
 	"time"
 
 	"github.com/pkg/errors"
-	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/klog/v2"
 
 	pool "gopkg.in/go-playground/pool.v3"
@@ -143,7 +142,7 @@ func (s *statusSync) sync(key interface{}) error {
 	if err != nil {
 		return err
 	}
-	s.updateStatus(sliceToStatus(addrs))
+	s.updateStatus(standardizeLoadBalancerIngresses(addrs))
 
 	return nil
 }
@@ -162,13 +161,25 @@ func NewStatusSyncer(config Config) Syncer {
 	return st
 }
 
+func nameOrIPToLoadBalancerIngress(nameOrIP string) apiv1.LoadBalancerIngress {
+	if net.ParseIP(nameOrIP) != nil {
+		return apiv1.LoadBalancerIngress{IP: nameOrIP}
+	}
+
+	return apiv1.LoadBalancerIngress{Hostname: nameOrIP}
+}
+
 // runningAddresses returns a list of IP addresses and/or FQDN where the
 // ingress controller is currently running
-func (s *statusSync) runningAddresses() ([]string, error) {
+func (s *statusSync) runningAddresses() ([]apiv1.LoadBalancerIngress, error) {
 	if s.PublishStatusAddress != "" {
 		re := regexp.MustCompile(`,\s*`)
 		multipleAddrs := re.Split(s.PublishStatusAddress, -1)
-		return multipleAddrs, nil
+		addrs := make([]apiv1.LoadBalancerIngress, len(multipleAddrs))
+		for i, addr := range multipleAddrs {
+			addrs[i] = nameOrIPToLoadBalancerIngress(addr)
+		}
+		return addrs, nil
 	}
 
 	if s.PublishService != "" {
@@ -183,7 +194,7 @@ func (s *statusSync) runningAddresses() ([]string, error) {
 		return nil, err
 	}
 
-	addrs := make([]string, 0)
+	addrs := make([]apiv1.LoadBalancerIngress, 0)
 	for i := range pods.Items {
 		pod := pods.Items[i]
 		// only Running pods are valid
@@ -206,8 +217,8 @@ func (s *statusSync) runningAddresses() ([]string, error) {
 		}
 
 		name := k8s.GetNodeIPOrName(s.Client, pod.Spec.NodeName, s.UseNodeInternalIP)
-		if !stringInSlice(name, addrs) {
-			addrs = append(addrs, name)
+		if !stringInIngresses(name, addrs) {
+			addrs = append(addrs, nameOrIPToLoadBalancerIngress(name))
 		}
 	}
 
@@ -225,17 +236,9 @@ func (s *statusSync) isRunningMultiplePods() bool {
 	return len(pods.Items) > 1
 }
 
-// sliceToStatus converts a slice of IP and/or hostnames to LoadBalancerIngress
-func sliceToStatus(endpoints []string) []apiv1.LoadBalancerIngress {
-	lbi := []apiv1.LoadBalancerIngress{}
-	for _, ep := range endpoints {
-		if net.ParseIP(ep) == nil {
-			lbi = append(lbi, apiv1.LoadBalancerIngress{Hostname: ep})
-		} else {
-			lbi = append(lbi, apiv1.LoadBalancerIngress{IP: ep})
-		}
-	}
-
+// standardizeLoadBalancerIngresses sorts the list of loadbalancer by
+// IP
+func standardizeLoadBalancerIngresses(lbi []apiv1.LoadBalancerIngress) []apiv1.LoadBalancerIngress {
 	sort.SliceStable(lbi, func(a, b int) bool {
 		return lbi[a].IP < lbi[b].IP
 	})
@@ -321,7 +324,7 @@ func ingressSliceEqual(lhs, rhs []apiv1.LoadBalancerIngress) bool {
 	return true
 }
 
-func statusAddressFromService(service string, kubeClient clientset.Interface) ([]string, error) {
+func statusAddressFromService(service string, kubeClient clientset.Interface) ([]apiv1.LoadBalancerIngress, error) {
 	ns, name, _ := k8s.ParseNameNS(service)
 	svc, err := kubeClient.CoreV1().Services(ns).Get(context.TODO(), name, metav1.GetOptions{})
 	if err != nil {
@@ -330,39 +333,50 @@ func statusAddressFromService(service string, kubeClient clientset.Interface) ([
 
 	switch svc.Spec.Type {
 	case apiv1.ServiceTypeExternalName:
-		return []string{svc.Spec.ExternalName}, nil
+		return []apiv1.LoadBalancerIngress{{
+			Hostname: svc.Spec.ExternalName,
+		}}, nil
 	case apiv1.ServiceTypeClusterIP:
-		return []string{svc.Spec.ClusterIP}, nil
+		return []apiv1.LoadBalancerIngress{{
+			IP: svc.Spec.ClusterIP,
+		}}, nil
 	case apiv1.ServiceTypeNodePort:
-		addresses := sets.NewString()
-		if svc.Spec.ExternalIPs != nil {
-			addresses.Insert(svc.Spec.ExternalIPs...)
-		} else {
-			addresses.Insert(svc.Spec.ClusterIP)
+		if svc.Spec.ExternalIPs == nil {
+			return []apiv1.LoadBalancerIngress{{
+				IP: svc.Spec.ClusterIP,
+			}}, nil
+		}
+		addrs := make([]apiv1.LoadBalancerIngress, len(svc.Spec.ExternalIPs))
+		for i, ip := range svc.Spec.ExternalIPs {
+			addrs[i] = apiv1.LoadBalancerIngress{IP: ip}
 		}
-		return addresses.List(), nil
+		return addrs, nil
 	case apiv1.ServiceTypeLoadBalancer:
-		addresses := sets.NewString()
-		for _, ip := range svc.Status.LoadBalancer.Ingress {
-			if ip.IP == "" {
-				addresses.Insert(ip.Hostname)
-			} else {
-				addresses.Insert(ip.IP)
+		addrs := make([]apiv1.LoadBalancerIngress, len(svc.Status.LoadBalancer.Ingress))
+		for i, ingress := range svc.Status.LoadBalancer.Ingress {
+			addrs[i] = apiv1.LoadBalancerIngress{}
+			if ingress.Hostname != "" {
+				addrs[i].Hostname = ingress.Hostname
+			}
+			if ingress.IP != "" {
+				addrs[i].IP = ingress.IP
 			}
 		}
-
-		addresses.Insert(svc.Spec.ExternalIPs...)
-
-		return addresses.List(), nil
+		for _, ip := range svc.Spec.ExternalIPs {
+			if !stringInIngresses(ip, addrs) {
+				addrs = append(addrs, apiv1.LoadBalancerIngress{IP: ip})
+			}
+		}
+		return addrs, nil
 	}
 
 	return nil, fmt.Errorf("unable to extract IP address/es from service %v", service)
 }
 
 // stringInSlice returns true if s is in list
-func stringInSlice(s string, list []string) bool {
+func stringInIngresses(s string, list []apiv1.LoadBalancerIngress) bool {
 	for _, v := range list {
-		if v == s {
+		if v.IP == s || v.Hostname == s {
 			return true
 		}
 	}
diff --git a/rootfs/etc/nginx/lua/plugins/README.md b/rootfs/etc/nginx/lua/plugins/README.md
index 5178f01f0..0626a48ff 100644
--- a/rootfs/etc/nginx/lua/plugins/README.md
+++ b/rootfs/etc/nginx/lua/plugins/README.md
@@ -33,4 +33,4 @@ Mounting is the quickest option.
 
 ### Enabling plugins
 
-Once your plugin is ready you need to use [`plugins` configuration setting](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#plugins) to activate it. Let's say you want to active `hello_world` and `open_idc` plugins, then you set `plugins` setting to `"hello_world, open_idc"`. _Note_ that the plugins will be executed in the given order.
+Once your plugin is ready you need to use [`plugins` configuration setting](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#plugins) to activate it. Let's say you want to activate `hello_world` and `open_idc` plugins, then you set `plugins` setting to `"hello_world, open_idc"`. _Note_ that the plugins will be executed in the given order.


Test patch:
diff --git a/images/kube-webhook-certgen/rootfs/pkg/certs/certs_test.go b/images/kube-webhook-certgen/rootfs/pkg/certs/certs_test.go
index e8fa0bb18..b4d95b9b2 100644
--- a/images/kube-webhook-certgen/rootfs/pkg/certs/certs_test.go
+++ b/images/kube-webhook-certgen/rootfs/pkg/certs/certs_test.go
@@ -16,7 +16,6 @@ func handler(w http.ResponseWriter, r *http.Request) {
 }
 
 func TestCertificateCreation(t *testing.T) {
-
 	ca, cert, key := GenerateCerts("localhost")
 
 	c, err := tls.X509KeyPair(cert, key)
@@ -30,7 +29,9 @@ func TestCertificateCreation(t *testing.T) {
 	tr := &http.Transport{
 		TLSClientConfig: &tls.Config{
 			RootCAs:    caCertPool,
-			ServerName: "localhost"}}
+			ServerName: "localhost",
+		},
+	}
 
 	ts := httptest.NewUnstartedServer(http.HandlerFunc(handler))
 	ts.TLS = &tls.Config{Certificates: []tls.Certificate{c}}
diff --git a/images/kube-webhook-certgen/rootfs/pkg/k8s/k8s_test.go b/images/kube-webhook-certgen/rootfs/pkg/k8s/k8s_test.go
index d2a82682f..1a7444613 100644
--- a/images/kube-webhook-certgen/rootfs/pkg/k8s/k8s_test.go
+++ b/images/kube-webhook-certgen/rootfs/pkg/k8s/k8s_test.go
@@ -99,7 +99,8 @@ func TestPatchWebhookConfigurations(t *testing.T) {
 			ObjectMeta: metav1.ObjectMeta{
 				Name: testWebhookName,
 			},
-			Webhooks: []admissionv1.MutatingWebhook{{Name: "m1"}, {Name: "m2"}}}, metav1.CreateOptions{})
+			Webhooks: []admissionv1.MutatingWebhook{{Name: "m1"}, {Name: "m2"}},
+		}, metav1.CreateOptions{})
 
 	k.clientset.
 		AdmissionregistrationV1().
@@ -109,7 +110,8 @@ func TestPatchWebhookConfigurations(t *testing.T) {
 			ObjectMeta: metav1.ObjectMeta{
 				Name: testWebhookName,
 			},
-			Webhooks: []admissionv1.ValidatingWebhook{{Name: "v1"}, {Name: "v2"}}}, metav1.CreateOptions{})
+			Webhooks: []admissionv1.ValidatingWebhook{{Name: "v1"}, {Name: "v2"}},
+		}, metav1.CreateOptions{})
 
 	k.PatchWebhookConfigurations(testWebhookName, ca, &fail, true, true)
 
@@ -117,16 +119,14 @@ func TestPatchWebhookConfigurations(t *testing.T) {
 		AdmissionregistrationV1().
 		MutatingWebhookConfigurations().
 		Get(context.Background(), testWebhookName, metav1.GetOptions{})
-
 	if err != nil {
 		t.Error(err)
 	}
 
 	whval, err := k.clientset.
-		AdmissionregistrationV1beta1().
+		AdmissionregistrationV1().
 		MutatingWebhookConfigurations().
 		Get(context.Background(), testWebhookName, metav1.GetOptions{})
-
 	if err != nil {
 		t.Error(err)
 	}
@@ -155,5 +155,4 @@ func TestPatchWebhookConfigurations(t *testing.T) {
 	if whval.Webhooks[1].FailurePolicy == nil {
 		t.Errorf("Expected second validating webhook failure policy to be set to %s", fail)
 	}
-
 }
diff --git a/images/test-runner/Makefile b/images/test-runner/Makefile
index 0ba3561b0..12bb7429b 100644
--- a/images/test-runner/Makefile
+++ b/images/test-runner/Makefile
@@ -23,7 +23,7 @@ REGISTRY ?= local
 
 IMAGE = $(REGISTRY)/e2e-test-runner
 
-NGINX_BASE_IMAGE ?= k8s.gcr.io/ingress-nginx/nginx:v20210904-gb7c973dce@sha256:fac972a7e43b18408ecb9e87da868df519428294e2e988c16be72479ee873c0e
+NGINX_BASE_IMAGE ?= k8s.gcr.io/ingress-nginx/nginx:v20210915-g498892514@sha256:8c1e48123e64e3f2b90ed32a53babd9b5f5431dad26beecdcb8fc185ded3b6dd
 
 # required to enable buildx
 export DOCKER_CLI_EXPERIMENTAL=enabled
diff --git a/images/test-runner/cloudbuild.yaml b/images/test-runner/cloudbuild.yaml
index 3e4d67d06..7258ab384 100644
--- a/images/test-runner/cloudbuild.yaml
+++ b/images/test-runner/cloudbuild.yaml
@@ -1,4 +1,4 @@
-timeout: 1200s
+timeout: 3600s
 options:
   substitution_option: ALLOW_LOOSE
 steps:
diff --git a/internal/ingress/annotations/authtls/main_test.go b/internal/ingress/annotations/authtls/main_test.go
index b2c915d18..f7649fe1c 100644
--- a/internal/ingress/annotations/authtls/main_test.go
+++ b/internal/ingress/annotations/authtls/main_test.go
@@ -94,10 +94,6 @@ func TestAnnotations(t *testing.T) {
 	data := map[string]string{}
 
 	data[parser.GetAnnotationWithPrefix("auth-tls-secret")] = "default/demo-secret"
-	data[parser.GetAnnotationWithPrefix("auth-tls-verify-client")] = "off"
-	data[parser.GetAnnotationWithPrefix("auth-tls-verify-depth")] = "1"
-	data[parser.GetAnnotationWithPrefix("auth-tls-error-page")] = "ok.com/error"
-	data[parser.GetAnnotationWithPrefix("auth-tls-pass-certificate-to-upstream")] = "true"
 
 	ing.SetAnnotations(data)
 
@@ -120,12 +116,45 @@ func TestAnnotations(t *testing.T) {
 	if u.AuthSSLCert.Secret != secret.Secret {
 		t.Errorf("expected %v but got %v", secret.Secret, u.AuthSSLCert.Secret)
 	}
-	if u.VerifyClient != "off" {
-		t.Errorf("expected %v but got %v", "off", u.VerifyClient)
+	if u.VerifyClient != "on" {
+		t.Errorf("expected %v but got %v", "on", u.VerifyClient)
 	}
 	if u.ValidationDepth != 1 {
 		t.Errorf("expected %v but got %v", 1, u.ValidationDepth)
 	}
+	if u.ErrorPage != "" {
+		t.Errorf("expected %v but got %v", "", u.ErrorPage)
+	}
+	if u.PassCertToUpstream != false {
+		t.Errorf("expected %v but got %v", false, u.PassCertToUpstream)
+	}
+
+	data[parser.GetAnnotationWithPrefix("auth-tls-verify-client")] = "off"
+	data[parser.GetAnnotationWithPrefix("auth-tls-verify-depth")] = "2"
+	data[parser.GetAnnotationWithPrefix("auth-tls-error-page")] = "ok.com/error"
+	data[parser.GetAnnotationWithPrefix("auth-tls-pass-certificate-to-upstream")] = "true"
+
+	ing.SetAnnotations(data)
+
+	i, err = NewParser(fakeSecret).Parse(ing)
+	if err != nil {
+		t.Errorf("Unexpected error with ingress: %v", err)
+	}
+
+	u, ok = i.(*Config)
+	if !ok {
+		t.Errorf("expected *Config but got %v", u)
+	}
+
+	if u.AuthSSLCert.Secret != secret.Secret {
+		t.Errorf("expected %v but got %v", secret.Secret, u.AuthSSLCert.Secret)
+	}
+	if u.VerifyClient != "off" {
+		t.Errorf("expected %v but got %v", "off", u.VerifyClient)
+	}
+	if u.ValidationDepth != 2 {
+		t.Errorf("expected %v but got %v", 2, u.ValidationDepth)
+	}
 	if u.ErrorPage != "ok.com/error" {
 		t.Errorf("expected %v but got %v", "ok.com/error", u.ErrorPage)
 	}
diff --git a/internal/ingress/annotations/serviceupstream/main_test.go b/internal/ingress/annotations/serviceupstream/main_test.go
index 3fbdb5785..b773e9723 100644
--- a/internal/ingress/annotations/serviceupstream/main_test.go
+++ b/internal/ingress/annotations/serviceupstream/main_test.go
@@ -23,6 +23,7 @@ import (
 	networking "k8s.io/api/networking/v1"
 	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
+	"k8s.io/ingress-nginx/internal/ingress/defaults"
 	"k8s.io/ingress-nginx/internal/ingress/resolver"
 )
 
@@ -119,3 +120,52 @@ func TestIngressAnnotationServiceUpstreamSetFalse(t *testing.T) {
 		t.Errorf("expected annotation value to be false, got true")
 	}
 }
+
+type mockBackend struct {
+	resolver.Mock
+}
+
+// GetDefaultBackend returns the backend that must be used as default
+func (m mockBackend) GetDefaultBackend() defaults.Backend {
+	return defaults.Backend{
+		ServiceUpstream: true,
+	}
+}
+
+// Test that when we have a default configuration set on the Backend that is used
+// when we don't have the annotation
+func TestParseAnnotationsWithDefaultConfig(t *testing.T) {
+	ing := buildIngress()
+
+	val, _ := NewParser(mockBackend{}).Parse(ing)
+	enabled, ok := val.(bool)
+
+	if !ok {
+		t.Errorf("expected a bool type")
+	}
+
+	if !enabled {
+		t.Errorf("expected annotation value to be true, got false")
+	}
+}
+
+// Test that the annotation will disable the service upstream when enabled
+// in the default configuration
+func TestParseAnnotationsOverridesDefaultConfig(t *testing.T) {
+	ing := buildIngress()
+
+	data := map[string]string{}
+	data[parser.GetAnnotationWithPrefix("service-upstream")] = "false"
+	ing.SetAnnotations(data)
+
+	val, _ := NewParser(mockBackend{}).Parse(ing)
+	enabled, ok := val.(bool)
+
+	if !ok {
+		t.Errorf("expected a bool type")
+	}
+
+	if enabled {
+		t.Errorf("expected annotation value to be false, got true")
+	}
+}
diff --git a/internal/ingress/controller/endpoints_test.go b/internal/ingress/controller/endpoints_test.go
index 20d53c526..83c8e1837 100644
--- a/internal/ingress/controller/endpoints_test.go
+++ b/internal/ingress/controller/endpoints_test.go
@@ -315,7 +315,50 @@ func TestGetEndpoints(t *testing.T) {
 			[]ingress.Endpoint{},
 		},
 		{
-			"should return no endpoint when the name of the port name do not match any port in the endpoint Subsets",
+			"should return no endpoint when the name of the port name do not match any port in the endpoint Subsets and TargetPort is string",
+			&corev1.Service{
+				Spec: corev1.ServiceSpec{
+					Type:      corev1.ServiceTypeClusterIP,
+					ClusterIP: "1.1.1.1",
+					Ports: []corev1.ServicePort{
+						{
+							Name:       "default",
+							TargetPort: intstr.FromString("port-1"),
+						},
+					},
+				},
+			},
+			&corev1.ServicePort{
+				Name:       "default",
+				TargetPort: intstr.FromString("port-1"),
+			},
+			corev1.ProtocolTCP,
+			func(string) (*corev1.Endpoints, error) {
+				nodeName := "dummy"
+				return &corev1.Endpoints{
+					Subsets: []corev1.EndpointSubset{
+						{
+							Addresses: []corev1.EndpointAddress{
+								{
+									IP:       "1.1.1.1",
+									NodeName: &nodeName,
+								},
+							},
+							Ports: []corev1.EndpointPort{
+								{
+									Protocol: corev1.ProtocolTCP,
+									Port:     int32(80),
+									Name:     "another-name",
+								},
+							},
+						},
+					},
+				}, nil
+			},
+			[]ingress.Endpoint{},
+		},
+		{
+			"should return one endpoint when the name of the port name do not match any port in the endpoint Subsets and TargetPort is int",
 			&corev1.Service{
 				Spec: corev1.ServiceSpec{
 					Type:      corev1.ServiceTypeClusterIP,
@@ -355,7 +398,12 @@ func TestGetEndpoints(t *testing.T) {
 					},
 				}, nil
 			},
-			[]ingress.Endpoint{},
+			[]ingress.Endpoint{
+				{
+					Address: "1.1.1.1",
+					Port:    "80",
+				},
+			},
 		},
 		{
 			"should return one endpoint when the name of the port name match a port in the endpoint Subsets",
diff --git a/internal/ingress/controller/store/store_test.go b/internal/ingress/controller/store/store_test.go
index 35e5955b1..9004094a3 100644
--- a/internal/ingress/controller/store/store_test.go
+++ b/internal/ingress/controller/store/store_test.go
@@ -316,7 +316,7 @@ func TestStore(t *testing.T) {
 
 		err := framework.WaitForIngressInNamespace(clientSet, ns, ing.Name)
 		if err != nil {
-			t.Errorf("error waiting for secret: %v", err)
+			t.Errorf("error waiting for ingress: %v", err)
 		}
 		time.Sleep(1 * time.Second)
 
@@ -486,6 +486,112 @@ func TestStore(t *testing.T) {
 		}
 	})
 
+	t.Run("should return two events for add and delete and one for update of ingress and watch-ingress-by-name", func(t *testing.T) {
+		ns := createNamespace(clientSet, t)
+		defer deleteNamespace(ns, clientSet, t)
+		ic := createIngressClass(clientSet, t, "not-k8s.io/by-name")
+		defer deleteIngressClass(ic, clientSet, t)
+
+		createConfigMap(clientSet, ns, t)
+
+		stopCh := make(chan struct{})
+		updateCh := channels.NewRingChannel(1024)
+
+		var add uint64
+		var upd uint64
+		var del uint64
+
+		go func(ch *channels.RingChannel) {
+			for {
+				evt, ok := <-ch.Out()
+				if !ok {
+					return
+				}
+
+				e := evt.(Event)
+				if e.Obj == nil {
+					continue
+				}
+				if _, ok := e.Obj.(*networking.Ingress); !ok {
+					continue
+				}
+
+				switch e.Type {
+				case CreateEvent:
+					atomic.AddUint64(&add, 1)
+				case UpdateEvent:
+					atomic.AddUint64(&upd, 1)
+				case DeleteEvent:
+					atomic.AddUint64(&del, 1)
+				}
+			}
+		}(updateCh)
+
+		ingressClassconfig := &ingressclass.IngressClassConfiguration{
+			Controller:         ingressclass.DefaultControllerName,
+			AnnotationValue:    ic,
+			IngressClassByName: true,
+		}
+
+		storer := New(
+			ns,
+			fmt.Sprintf("%v/config", ns),
+			fmt.Sprintf("%v/tcp", ns),
+			fmt.Sprintf("%v/udp", ns),
+			"",
+			10*time.Minute,
+			clientSet,
+			updateCh,
+			false,
+			ingressClassconfig)
+
+		storer.Run(stopCh)
+		validSpec := commonIngressSpec
+		validSpec.IngressClassName = &ic
+		ing := ensureIngress(&networking.Ingress{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "ingclass-by-name",
+				Namespace: ns,
+			},
+			Spec: validSpec,
+		}, clientSet, t)
+
+		err := framework.WaitForIngressInNamespace(clientSet, ns, ing.Name)
+		if err != nil {
+			t.Errorf("error waiting for ingress: %v", err)
+		}
+		time.Sleep(1 * time.Second)
+
+		ingressUpdated := ing.DeepCopy()
+		ingressUpdated.Spec.Rules[0].Host = "update-dummy"
+		_ = ensureIngress(ingressUpdated, clientSet, t)
+		if err != nil {
+			t.Errorf("error updating ingress: %v", err)
+		}
+		// Secret takes a bit to update
+		time.Sleep(3 * time.Second)
+
+		err = clientSet.NetworkingV1().Ingresses(ingressUpdated.Namespace).Delete(context.TODO(), ingressUpdated.Name, metav1.DeleteOptions{})
+		if err != nil {
+			t.Errorf("error deleting ingress: %v", err)
+		}
+
+		err = framework.WaitForNoIngressInNamespace(clientSet, ingressUpdated.Namespace, ingressUpdated.Name)
+		if err != nil {
+			t.Errorf("error waiting for ingress deletion: %v", err)
+		}
+
+		if atomic.LoadUint64(&add) != 1 {
+			t.Errorf("expected 1 event of type Create but %v occurred", add)
+		}
+		if atomic.LoadUint64(&upd) != 1 {
+			t.Errorf("expected 1 event of type Update but %v occurred", upd)
+		}
+		if atomic.LoadUint64(&del) != 1 {
+			t.Errorf("expected 1 event of type Delete but %v occurred", del)
+		}
+	})
+
 	t.Run("should not receive updates for ingress with invalid class annotation", func(t *testing.T) {
 		ns := createNamespace(clientSet, t)
 		defer deleteNamespace(ns, clientSet, t)
diff --git a/internal/ingress/status/status_test.go b/internal/ingress/status/status_test.go
index 79bb85891..fefca5ff2 100644
--- a/internal/ingress/status/status_test.go
+++ b/internal/ingress/status/status_test.go
@@ -382,7 +382,7 @@ func TestKeyfunc(t *testing.T) {
 func TestRunningAddressesWithPublishService(t *testing.T) {
 	testCases := map[string]struct {
 		fakeClient  *testclient.Clientset
-		expected    []string
+		expected    []apiv1.LoadBalancerIngress
 		errExpected bool
 	}{
 		"service type ClusterIP": {
@@ -416,7 +416,9 @@ func TestRunningAddressesWithPublishService(t *testing.T) {
 				},
 				},
 			),
-			[]string{"1.1.1.1"},
+			[]apiv1.LoadBalancerIngress{
+				{IP: "1.1.1.1"},
+			},
 			false,
 		},
 		"service type NodePort": {
@@ -435,7 +437,9 @@ func TestRunningAddressesWithPublishService(t *testing.T) {
 				},
 				},
 			),
-			[]string{"1.1.1.1"},
+			[]apiv1.LoadBalancerIngress{
+				{IP: "1.1.1.1"},
+			},
 			false,
 		},
 		"service type ExternalName": {
@@ -454,7 +458,9 @@ func TestRunningAddressesWithPublishService(t *testing.T) {
 				},
 				},
 			),
-			[]string{"foo.bar"},
+			[]apiv1.LoadBalancerIngress{
+				{Hostname: "foo.bar"},
+			},
 			false,
 		},
 		"service type LoadBalancer": {
@@ -478,6 +484,10 @@ func TestRunningAddressesWithPublishService(t *testing.T) {
 										IP:       "",
 										Hostname: "foo",
 									},
+									{
+										IP:       "10.0.0.2",
+										Hostname: "10-0-0-2.cloudprovider.example.net",
+									},
 								},
 							},
 						},
@@ -485,7 +495,14 @@ func TestRunningAddressesWithPublishService(t *testing.T) {
 				},
 				},
 			),
-			[]string{"10.0.0.1", "foo"},
+			[]apiv1.LoadBalancerIngress{
+				{IP: "10.0.0.1"},
+				{Hostname: "foo"},
+				{
+					IP:       "10.0.0.2",
+					Hostname: "10-0-0-2.cloudprovider.example.net",
+				},
+			},
 			false,
 		},
 		"service type LoadBalancer with same externalIP and ingress IP": {
@@ -513,7 +530,9 @@ func TestRunningAddressesWithPublishService(t *testing.T) {
 				},
 				},
 			),
-			[]string{"10.0.0.1"},
+			[]apiv1.LoadBalancerIngress{
+				{IP: "10.0.0.1"},
+			},
 			false,
 		},
 		"invalid service type": {
@@ -549,7 +568,7 @@ func TestRunningAddressesWithPublishService(t *testing.T) {
 			}
 
 			if ra == nil {
-				t.Fatalf("returned nil but expected valid []string")
+				t.Fatalf("returned nil but expected valid []apiv1.LoadBalancerIngress")
 			}
 
 			if !reflect.DeepEqual(tc.expected, ra) {
@@ -565,15 +584,15 @@ func TestRunningAddressesWithPods(t *testing.T) {
 
 	r, _ := fk.runningAddresses()
 	if r == nil {
-		t.Fatalf("returned nil but expected valid []string")
+		t.Fatalf("returned nil but expected valid []apiv1.LoadBalancerIngress")
 	}
 	rl := len(r)
 	if len(r) != 1 {
 		t.Fatalf("returned %v but expected %v", rl, 1)
 	}
 	rv := r[0]
-	if rv != "11.0.0.2" {
-		t.Errorf("returned %v but expected %v", rv, "11.0.0.2")
+	if rv.IP != "11.0.0.2" {
+		t.Errorf("returned %v but expected %v", rv, apiv1.LoadBalancerIngress{IP: "11.0.0.2"})
 	}
 }
 
@@ -583,15 +602,15 @@ func TestRunningAddressesWithPublishStatusAddress(t *testing.T) {
 
 	ra, _ := fk.runningAddresses()
 	if ra == nil {
-		t.Fatalf("returned nil but expected valid []string")
+		t.Fatalf("returned nil but expected valid []apiv1.LoadBalancerIngress")
 	}
 	rl := len(ra)
 	if len(ra) != 1 {
 		t.Errorf("returned %v but expected %v", rl, 1)
 	}
 	rv := ra[0]
-	if rv != "127.0.0.1" {
-		t.Errorf("returned %v but expected %v", rv, "127.0.0.1")
+	if rv.IP != "127.0.0.1" {
+		t.Errorf("returned %v but expected %v", rv, apiv1.LoadBalancerIngress{IP: "127.0.0.1"})
 	}
 }
 
@@ -601,7 +620,7 @@ func TestRunningAddressesWithPublishStatusAddresses(t *testing.T) {
 
 	ra, _ := fk.runningAddresses()
 	if ra == nil {
-		t.Fatalf("returned nil but expected valid []string")
+		t.Fatalf("returned nil but expected valid []apiv1.LoadBalancerIngress")
 	}
 	rl := len(ra)
 	if len(ra) != 2 {
@@ -609,11 +628,11 @@ func TestRunningAddressesWithPublishStatusAddresses(t *testing.T) {
 	}
 	rv := ra[0]
 	rv2 := ra[1]
-	if rv != "127.0.0.1" {
-		t.Errorf("returned %v but expected %v", rv, "127.0.0.1")
+	if rv.IP != "127.0.0.1" {
+		t.Errorf("returned %v but expected %v", rv, apiv1.LoadBalancerIngress{IP: "127.0.0.1"})
 	}
-	if rv2 != "1.1.1.1" {
-		t.Errorf("returned %v but expected %v", rv2, "1.1.1.1")
+	if rv2.IP != "1.1.1.1" {
+		t.Errorf("returned %v but expected %v", rv2, apiv1.LoadBalancerIngress{IP: "1.1.1.1"})
 	}
 }
 
@@ -623,7 +642,7 @@ func TestRunningAddressesWithPublishStatusAddressesAndSpaces(t *testing.T) {
 
 	ra, _ := fk.runningAddresses()
 	if ra == nil {
-		t.Fatalf("returned nil but expected valid []string")
+		t.Fatalf("returned nil but expected valid []apiv1.LoadBalancerIngresst")
 	}
 	rl := len(ra)
 	if len(ra) != 2 {
@@ -631,22 +650,22 @@ func TestRunningAddressesWithPublishStatusAddressesAndSpaces(t *testing.T) {
 	}
 	rv := ra[0]
 	rv2 := ra[1]
-	if rv != "127.0.0.1" {
-		t.Errorf("returned %v but expected %v", rv, "127.0.0.1")
+	if rv.IP != "127.0.0.1" {
+		t.Errorf("returned %v but expected %v", rv, apiv1.LoadBalancerIngress{IP: "127.0.0.1"})
 	}
-	if rv2 != "1.1.1.1" {
-		t.Errorf("returned %v but expected %v", rv2, "1.1.1.1")
+	if rv2.IP != "1.1.1.1" {
+		t.Errorf("returned %v but expected %v", rv2, apiv1.LoadBalancerIngress{IP: "1.1.1.1"})
 	}
 }
 
-func TestSliceToStatus(t *testing.T) {
-	fkEndpoints := []string{
-		"10.0.0.1",
-		"2001:db8::68",
-		"opensource-k8s-ingress",
+func TestStandardizeLoadBalancerIngresses(t *testing.T) {
+	fkEndpoints := []apiv1.LoadBalancerIngress{
+		{IP: "2001:db8::68"},
+		{IP: "10.0.0.1"},
+		{Hostname: "opensource-k8s-ingress"},
 	}
 
-	r := sliceToStatus(fkEndpoints)
+	r := standardizeLoadBalancerIngresses(fkEndpoints)
 
 	if r == nil {
 		t.Fatalf("returned nil but expected a valid []apiv1.LoadBalancerIngress")
diff --git a/test/e2e-image/Dockerfile b/test/e2e-image/Dockerfile
index 28cd272b6..fc5f30000 100644
--- a/test/e2e-image/Dockerfile
+++ b/test/e2e-image/Dockerfile
@@ -1,4 +1,4 @@
-FROM k8s.gcr.io/ingress-nginx/e2e-test-runner:v20210822-g5e5faa24d@sha256:55c568d9e35e15d94b3ab41fe549b8ee4cd910cc3e031ddcccd06256755c5d89 AS BASE
+FROM k8s.gcr.io/ingress-nginx/e2e-test-runner:v20210916-gd9f96bbbb@sha256:5b434c08e582b58b96867152682c1e754ee609c82390abf3074992d4ec53ed25 AS BASE
 
 FROM alpine:3.12
 
diff --git a/test/e2e/annotations/affinity.go b/test/e2e/annotations/affinity.go
index 998eca82f..479860043 100644
--- a/test/e2e/annotations/affinity.go
+++ b/test/e2e/annotations/affinity.go
@@ -18,6 +18,7 @@ package annotations
 
 import (
 	"context"
+	"crypto/tls"
 	"fmt"
 	"net/http"
 	"strings"
@@ -363,4 +364,73 @@ var _ = framework.DescribeAnnotation("affinity session-cookie-name", func() {
 			Header("Set-Cookie").Contains("SERVERID=")
 	})
 
+	ginkgo.It("should set secure in cookie with provided true annotation on http", func() {
+		host := "foo.com"
+		annotations := make(map[string]string)
+		annotations["nginx.ingress.kubernetes.io/affinity"] = "cookie"
+		annotations["nginx.ingress.kubernetes.io/session-cookie-name"] = "SERVERID"
+		annotations["nginx.ingress.kubernetes.io/session-cookie-secure"] = "true"
+
+		ing := framework.NewSingleIngress(host, "/bar", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.WaitForNginxServer(host,
+			func(server string) bool {
+				return strings.Contains(server, fmt.Sprintf("server_name %s ;", host))
+			})
+
+		f.HTTPTestClient().
+			GET("/bar").
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusOK).
+			Header("Set-Cookie").Contains("; Secure")
+	})
+
+	ginkgo.It("should not set secure in cookie with provided false annotation on http", func() {
+		host := "foo.com"
+		annotations := make(map[string]string)
+		annotations["nginx.ingress.kubernetes.io/affinity"] = "cookie"
+		annotations["nginx.ingress.kubernetes.io/session-cookie-name"] = "SERVERID"
+		annotations["nginx.ingress.kubernetes.io/session-cookie-secure"] = "false"
+
+		ing := framework.NewSingleIngress(host, "/bar", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.WaitForNginxServer(host,
+			func(server string) bool {
+				return strings.Contains(server, fmt.Sprintf("server_name %s ;", host))
+			})
+
+		f.HTTPTestClient().
+			GET("/bar").
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusOK).
+			Header("Set-Cookie").NotContains("; Secure")
+	})
+
+	ginkgo.It("should set secure in cookie with provided false annotation on https", func() {
+		host := "foo.com"
+		annotations := make(map[string]string)
+		annotations["nginx.ingress.kubernetes.io/affinity"] = "cookie"
+		annotations["nginx.ingress.kubernetes.io/session-cookie-name"] = "SERVERID"
+		annotations["nginx.ingress.kubernetes.io/session-cookie-secure"] = "false"
+
+		f.EnsureIngress(framework.NewSingleIngressWithTLS(host, "/", host, []string{host}, f.Namespace, framework.EchoService, 80, annotations))
+
+		f.WaitForNginxServer(host,
+			func(server string) bool {
+				return strings.Contains(server, fmt.Sprintf("server_name %s ;", host)) &&
+					strings.Contains(server, "listen 443")
+			})
+
+		f.HTTPTestClientWithTLSConfig(&tls.Config{ServerName: host, InsecureSkipVerify: true}).
+			GET("/").
+			WithURL(f.GetURL(framework.HTTPS)).
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusOK).
+			Header("Set-Cookie").Contains("; Secure")
+	})
 })
diff --git a/test/e2e/annotations/authtls.go b/test/e2e/annotations/authtls.go
index 86c5d9ec5..093afe14e 100644
--- a/test/e2e/annotations/authtls.go
+++ b/test/e2e/annotations/authtls.go
@@ -17,7 +17,6 @@ limitations under the License.
 package annotations
 
 import (
-	"crypto/tls"
 	"fmt"
 	"net/http"
 	"strings"
@@ -34,7 +33,7 @@ var _ = framework.DescribeAnnotation("auth-tls-*", func() {
 		f.NewEchoDeploymentWithReplicas(2)
 	})
 
-	ginkgo.It("should set valid auth-tls-secret", func() {
+	ginkgo.It("should set sslClientCertificate, sslVerifyClient and sslVerifyDepth with auth-tls-secret", func() {
 		host := "authtls.foo.com"
 		nameSpace := f.Namespace
 
@@ -45,16 +44,28 @@ var _ = framework.DescribeAnnotation("auth-tls-*", func() {
 			nameSpace)
 		assert.Nil(ginkgo.GinkgoT(), err)
 
-		annotations := map[string]string{
+		annotations := map[string]string{}
+
+		ing := f.EnsureIngress(framework.NewSingleIngressWithTLS(host, "/", host, []string{host}, nameSpace, framework.EchoService, 80, annotations))
+
+		f.WaitForNginxServer(host,
+			func(server string) bool {
+				return !strings.Contains(server, "ssl_client_certificate") &&
+					!strings.Contains(server, "ssl_verify_client") &&
+					!strings.Contains(server, "ssl_verify_depth")
+			})
+
+		annotations = map[string]string{
 			"nginx.ingress.kubernetes.io/auth-tls-secret": nameSpace + "/" + host,
 		}
 
-		f.EnsureIngress(framework.NewSingleIngressWithTLS(host, "/", host, []string{host}, nameSpace, framework.EchoService, 80, annotations))
+		ing.SetAnnotations(annotations)
+		f.UpdateIngress(ing)
 
 		assertSslClientCertificateConfig(f, host, "on", "1")
 
 		// Send Request without Client Certs
-		f.HTTPTestClientWithTLSConfig(&tls.Config{ServerName: host, InsecureSkipVerify: true}).
+		f.HTTPTestClient().
 			GET("/").
 			WithURL(f.GetURL(framework.HTTPS)).
 			WithHeader("Host", host).
@@ -100,7 +111,7 @@ var _ = framework.DescribeAnnotation("auth-tls-*", func() {
 			Status(http.StatusOK)
 	})
 
-	ginkgo.It("should set valid auth-tls-secret, pass certificate to upstream, and error page", func() {
+	ginkgo.It("should 302 redirect to error page instead of 400 when auth-tls-error-page is set", func() {
 		host := "authtls.foo.com"
 		nameSpace := f.Namespace
 
@@ -114,9 +125,8 @@ var _ = framework.DescribeAnnotation("auth-tls-*", func() {
 		assert.Nil(ginkgo.GinkgoT(), err)
 
 		annotations := map[string]string{
-			"nginx.ingress.kubernetes.io/auth-tls-secret":                       nameSpace + "/" + host,
-			"nginx.ingress.kubernetes.io/auth-tls-error-page":                   f.GetURL(framework.HTTP) + errorPath,
-			"nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream": "true",
+			"nginx.ingress.kubernetes.io/auth-tls-secret":     nameSpace + "/" + host,
+			"nginx.ingress.kubernetes.io/auth-tls-error-page": f.GetURL(framework.HTTP) + errorPath,
 		}
 
 		f.EnsureIngress(framework.NewSingleIngressWithTLS(host, "/", host, []string{host}, nameSpace, framework.EchoService, 80, annotations))
@@ -124,12 +134,10 @@ var _ = framework.DescribeAnnotation("auth-tls-*", func() {
 		assertSslClientCertificateConfig(f, host, "on", "1")
 
 		sslErrorPage := fmt.Sprintf("error_page 495 496 = %s;", f.GetURL(framework.HTTP)+errorPath)
-		sslUpstreamClientCert := "proxy_set_header ssl-client-cert $ssl_client_escaped_cert;"
 
 		f.WaitForNginxServer(host,
 			func(server string) bool {
-				return strings.Contains(server, sslErrorPage) &&
-					strings.Contains(server, sslUpstreamClientCert)
+				return strings.Contains(server, sslErrorPage)
 			})
 
 		// Send Request without Client Certs
@@ -150,6 +158,51 @@ var _ = framework.DescribeAnnotation("auth-tls-*", func() {
 			Status(http.StatusOK)
 	})
 
+	ginkgo.It("should pass URL-encoded certificate to upstream", func() {
+		host := "authtls.foo.com"
+		nameSpace := f.Namespace
+
+		clientConfig, err := framework.CreateIngressMASecret(
+			f.KubeClientSet,
+			host,
+			host,
+			nameSpace)
+		assert.Nil(ginkgo.GinkgoT(), err)
+
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/auth-tls-secret":                       nameSpace + "/" + host,
+			"nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream": "true",
+		}
+
+		f.EnsureIngress(framework.NewSingleIngressWithTLS(host, "/", host, []string{host}, nameSpace, framework.EchoService, 80, annotations))
+
+		assertSslClientCertificateConfig(f, host, "on", "1")
+
+		sslUpstreamClientCert := "proxy_set_header ssl-client-cert $ssl_client_escaped_cert;"
+
+		f.WaitForNginxServer(host,
+			func(server string) bool {
+				return strings.Contains(server, sslUpstreamClientCert)
+			})
+
+		// Send Request without Client Certs
+		f.HTTPTestClient().
+			GET("/").
+			WithURL(f.GetURL(framework.HTTPS)).
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusBadRequest)
+
+		// Send Request Passing the Client Certs
+		f.HTTPTestClientWithTLSConfig(clientConfig).
+			GET("/").
+			WithURL(f.GetURL(framework.HTTPS)).
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusOK).
+			Body().Contains("ssl-client-cert=-----BEGIN%20CERTIFICATE-----%0A")
+	})
+
 	ginkgo.It("should validate auth-tls-verify-client", func() {
 		host := "authtls.foo.com"
 		nameSpace := f.Namespace
diff --git a/test/e2e/annotations/modsecurity.go b/test/e2e/annotations/modsecurity/modsecurity.go
similarity index 99%
rename from test/e2e/annotations/modsecurity.go
rename to test/e2e/annotations/modsecurity/modsecurity.go
index d83803c93..fad3a2dee 100644
--- a/test/e2e/annotations/modsecurity.go
+++ b/test/e2e/annotations/modsecurity/modsecurity.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package annotations
+package modsecurity
 
 import (
 	"net/http"
diff --git a/test/e2e/annotations/serviceupstream.go b/test/e2e/annotations/serviceupstream.go
new file mode 100644
index 000000000..c0bf37603
--- /dev/null
+++ b/test/e2e/annotations/serviceupstream.go
@@ -0,0 +1,128 @@
+/*
+Copyright 2021 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 (
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/onsi/ginkgo"
+	"github.com/stretchr/testify/assert"
+
+	"k8s.io/ingress-nginx/test/e2e/framework"
+
+	"k8s.io/ingress-nginx/internal/nginx"
+)
+
+var _ = framework.DescribeAnnotation("service-upstream", func() {
+	f := framework.NewDefaultFramework("serviceupstream")
+	host := "serviceupstream"
+
+	ginkgo.BeforeEach(func() {
+		f.NewEchoDeployment()
+	})
+
+	ginkgo.Context("when using the default value (false) and enabling in the annotations", func() {
+		ginkgo.It("should use the Service Cluster IP and Port ", func() {
+			annotations := map[string]string{
+				"nginx.ingress.kubernetes.io/service-upstream": "true",
+			}
+
+			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, fmt.Sprintf("server_name %s", host))
+			})
+
+			ginkgo.By("checking if the service is reached")
+			f.HTTPTestClient().
+				GET("/").
+				WithHeader("Host", host).
+				Expect().
+				Status(http.StatusOK)
+
+			ginkgo.By("checking if the Service Cluster IP and Port are used")
+			s := f.GetService(f.Namespace, framework.EchoService)
+			curlCmd := fmt.Sprintf("curl --fail --silent http://localhost:%v/configuration/backends", nginx.StatusPort)
+			output, err := f.ExecIngressPod(curlCmd)
+			assert.Nil(ginkgo.GinkgoT(), err)
+			assert.Contains(ginkgo.GinkgoT(), output, fmt.Sprintf(`{"address":"%s"`, s.Spec.ClusterIP))
+		})
+	})
+
+	ginkgo.Context("when enabling in the configmap", func() {
+		ginkgo.It("should use the Service Cluster IP and Port ", func() {
+			annotations := map[string]string{}
+
+			f.UpdateNginxConfigMapData("service-upstream", "true")
+
+			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, fmt.Sprintf("server_name %s", host))
+			})
+
+			ginkgo.By("checking if the service is reached")
+			f.HTTPTestClient().
+				GET("/").
+				WithHeader("Host", host).
+				Expect().
+				Status(http.StatusOK)
+
+			ginkgo.By("checking if the Service Cluster IP and Port are used")
+			s := f.GetService(f.Namespace, framework.EchoService)
+			curlCmd := fmt.Sprintf("curl --fail --silent http://localhost:%v/configuration/backends", nginx.StatusPort)
+			output, err := f.ExecIngressPod(curlCmd)
+			assert.Nil(ginkgo.GinkgoT(), err)
+			assert.Contains(ginkgo.GinkgoT(), output, fmt.Sprintf(`{"address":"%s"`, s.Spec.ClusterIP))
+		})
+	})
+
+	ginkgo.Context("when enabling in the configmap and disabling in the annotations", func() {
+		ginkgo.It("should not use the Service Cluster IP and Port", func() {
+			annotations := map[string]string{
+				"nginx.ingress.kubernetes.io/service-upstream": "false",
+			}
+
+			f.UpdateNginxConfigMapData("service-upstream", "true")
+
+			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, fmt.Sprintf("server_name %s", host))
+			})
+
+			ginkgo.By("checking if the service is reached")
+			f.HTTPTestClient().
+				GET("/").
+				WithHeader("Host", host).
+				Expect().
+				Status(http.StatusOK)
+
+			ginkgo.By("checking if the Service Cluster IP and Port are not used")
+			s := f.GetService(f.Namespace, framework.EchoService)
+			curlCmd := fmt.Sprintf("curl --fail --silent http://localhost:%v/configuration/backends", nginx.StatusPort)
+			output, err := f.ExecIngressPod(curlCmd)
+			assert.Nil(ginkgo.GinkgoT(), err)
+			assert.NotContains(ginkgo.GinkgoT(), output, fmt.Sprintf(`{"address":"%s"`, s.Spec.ClusterIP))
+		})
+	})
+})
diff --git a/test/e2e/e2e.go b/test/e2e/e2e.go
index 9bb8937ea..c0829f5ee 100644
--- a/test/e2e/e2e.go
+++ b/test/e2e/e2e.go
@@ -32,6 +32,7 @@ import (
 	// tests to run
 	_ "k8s.io/ingress-nginx/test/e2e/admission"
 	_ "k8s.io/ingress-nginx/test/e2e/annotations"
+	_ "k8s.io/ingress-nginx/test/e2e/annotations/modsecurity"
 	_ "k8s.io/ingress-nginx/test/e2e/dbg"
 	_ "k8s.io/ingress-nginx/test/e2e/defaultbackend"
 	_ "k8s.io/ingress-nginx/test/e2e/gracefulshutdown"
@@ -42,6 +43,7 @@ import (
 	_ "k8s.io/ingress-nginx/test/e2e/security"
 	_ "k8s.io/ingress-nginx/test/e2e/servicebackend"
 	_ "k8s.io/ingress-nginx/test/e2e/settings"
+	_ "k8s.io/ingress-nginx/test/e2e/settings/modsecurity"
 	_ "k8s.io/ingress-nginx/test/e2e/settings/ocsp"
 	_ "k8s.io/ingress-nginx/test/e2e/ssl"
 	_ "k8s.io/ingress-nginx/test/e2e/status"
diff --git a/test/e2e/framework/deployment.go b/test/e2e/framework/deployment.go
index 95bf37d75..4f01fab9e 100644
--- a/test/e2e/framework/deployment.go
+++ b/test/e2e/framework/deployment.go
@@ -38,7 +38,7 @@ const SlowEchoService = "slow-echo"
 const HTTPBinService = "httpbin"
 
 // NginxBaseImage use for testing
-const NginxBaseImage = "k8s.gcr.io/ingress-nginx/nginx:v20210904-gb7c973dce@sha256:fac972a7e43b18408ecb9e87da868df519428294e2e988c16be72479ee873c0e"
+const NginxBaseImage = "k8s.gcr.io/ingress-nginx/nginx:v20210915-g498892514@sha256:8c1e48123e64e3f2b90ed32a53babd9b5f5431dad26beecdcb8fc185ded3b6dd"
 
 // NewEchoDeployment creates a new single replica deployment of the echoserver image in a particular namespace
 func (f *Framework) NewEchoDeployment() {
diff --git a/test/e2e/framework/k8s.go b/test/e2e/framework/k8s.go
index 14937e6bd..ea34960b6 100644
--- a/test/e2e/framework/k8s.go
+++ b/test/e2e/framework/k8s.go
@@ -102,16 +102,21 @@ func (f *Framework) UpdateIngress(ingress *networking.Ingress) *networking.Ingre
 	return ing
 }
 
+// GetService gets a Service object from the given namespace, name and returns it, throws error if it does not exist.
+func (f *Framework) GetService(namespace string, name string) *core.Service {
+	s, err := f.KubeClientSet.CoreV1().Services(namespace).Get(context.TODO(), name, metav1.GetOptions{})
+	assert.Nil(ginkgo.GinkgoT(), err, "getting service")
+	assert.NotNil(ginkgo.GinkgoT(), s, "expected a service but none returned")
+
+	return s
+}
+
 // EnsureService creates a Service object and returns it, throws error if it already exists.
 func (f *Framework) EnsureService(service *core.Service) *core.Service {
 	err := createServiceWithRetries(f.KubeClientSet, f.Namespace, service)
 	assert.Nil(ginkgo.GinkgoT(), err, "creating service")
 
-	s, err := f.KubeClientSet.CoreV1().Services(f.Namespace).Get(context.TODO(), service.Name, metav1.GetOptions{})
-	assert.Nil(ginkgo.GinkgoT(), err, "getting service")
-	assert.NotNil(ginkgo.GinkgoT(), s, "expected a service but none returned")
-
-	return s
+	return f.GetService(f.Namespace, service.Name)
 }
 
 // EnsureDeployment creates a Deployment object and returns it, throws error if it already exists.
diff --git a/test/e2e/servicebackend/service_externalname.go b/test/e2e/servicebackend/service_externalname.go
index 2c33c020f..d2a921cd3 100644
--- a/test/e2e/servicebackend/service_externalname.go
+++ b/test/e2e/servicebackend/service_externalname.go
@@ -34,6 +34,27 @@ import (
 	"k8s.io/ingress-nginx/test/e2e/framework"
 )
 
+func buildHTTPBinExternalNameService(f *framework.Framework, portName string) *corev1.Service {
+	return &corev1.Service{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      framework.HTTPBinService,
+			Namespace: f.Namespace,
+		},
+		Spec: corev1.ServiceSpec{
+			ExternalName: "httpbin.org",
+			Type:         corev1.ServiceTypeExternalName,
+			Ports: []corev1.ServicePort{
+				{
+					Name:       portName,
+					Port:       80,
+					TargetPort: intstr.FromInt(80),
+					Protocol:   "TCP",
+				},
+			},
+		},
+	}
+}
+
 var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() {
 	f := framework.NewDefaultFramework("type-externalname")
 
@@ -107,24 +128,7 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() {
 	ginkgo.It("should return 200 for service type=ExternalName with a port defined", func() {
 		host := "echo"
 
-		svc := &corev1.Service{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      framework.HTTPBinService,
-				Namespace: f.Namespace,
-			},
-			Spec: corev1.ServiceSpec{
-				ExternalName: "httpbin.org",
-				Type:         corev1.ServiceTypeExternalName,
-				Ports: []corev1.ServicePort{
-					{
-						Name:       host,
-						Port:       80,
-						TargetPort: intstr.FromInt(80),
-						Protocol:   "TCP",
-					},
-				},
-			},
-		}
+		svc := buildHTTPBinExternalNameService(f, host)
 		f.EnsureService(svc)
 
 		annotations := map[string]string{
@@ -179,24 +183,7 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() {
 	ginkgo.It("should return 200 for service type=ExternalName using a port name", func() {
 		host := "echo"
 
-		svc := &corev1.Service{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      framework.HTTPBinService,
-				Namespace: f.Namespace,
-			},
-			Spec: corev1.ServiceSpec{
-				ExternalName: "httpbin.org",
-				Type:         corev1.ServiceTypeExternalName,
-				Ports: []corev1.ServicePort{
-					{
-						Name:       host,
-						Port:       80,
-						TargetPort: intstr.FromInt(80),
-						Protocol:   "TCP",
-					},
-				},
-			},
-		}
+		svc := buildHTTPBinExternalNameService(f, host)
 		f.EnsureService(svc)
 
 		annotations := map[string]string{
@@ -260,24 +247,7 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() {
 	ginkgo.It("should update the external name after a service update", func() {
 		host := "echo"
 
-		svc := &corev1.Service{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      framework.HTTPBinService,
-				Namespace: f.Namespace,
-			},
-			Spec: corev1.ServiceSpec{
-				ExternalName: "httpbin.org",
-				Type:         corev1.ServiceTypeExternalName,
-				Ports: []corev1.ServicePort{
-					{
-						Name:       host,
-						Port:       80,
-						TargetPort: intstr.FromInt(80),
-						Protocol:   "TCP",
-					},
-				},
-			},
-		}
+		svc := buildHTTPBinExternalNameService(f, host)
 		f.EnsureService(svc)
 
 		annotations := map[string]string{
@@ -336,4 +306,50 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() {
 		assert.Nil(ginkgo.GinkgoT(), err)
 		assert.Contains(ginkgo.GinkgoT(), output, `{"address":"eu.httpbin.org"`)
 	})
+
+	ginkgo.It("should sync ingress on external name service addition/deletion", func() {
+		host := "echo"
+
+		// Create the Ingress first
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.HTTPBinService, 80, nil)
+		f.EnsureIngress(ing)
+
+		f.WaitForNginxServer(host,
+			func(server string) bool {
+				return strings.Contains(server, "proxy_pass http://upstream_balancer;")
+			})
+
+		// Nginx should return 503 without the underlying service being available
+		f.HTTPTestClient().
+			GET("/get").
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusServiceUnavailable)
+
+		// Now create the service
+		svc := buildHTTPBinExternalNameService(f, host)
+		f.EnsureService(svc)
+
+		framework.Sleep()
+
+		// 503 should change to 200 OK
+		f.HTTPTestClient().
+			GET("/get").
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusOK)
+
+		// And back to 503 after deleting the service
+
+		err := f.KubeClientSet.CoreV1().Services(f.Namespace).Delete(context.TODO(), framework.HTTPBinService, metav1.DeleteOptions{})
+		assert.Nil(ginkgo.GinkgoT(), err, "unexpected error deleting httpbin service")
+
+		framework.Sleep()
+
+		f.HTTPTestClient().
+			GET("/get").
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusServiceUnavailable)
+	})
 })
diff --git a/test/e2e/settings/ingress_class.go b/test/e2e/settings/ingress_class.go
index 3ba42f311..9740eef38 100644
--- a/test/e2e/settings/ingress_class.go
+++ b/test/e2e/settings/ingress_class.go
@@ -510,4 +510,72 @@ var _ = framework.IngressNginxDescribe("[Flag] ingress-class", func() {
 		})
 
 	})
+
+	ginkgo.Context("With ingress-class-by-name flag", func() {
+		ginkgo.BeforeEach(func() {
+			err := f.UpdateIngressControllerDeployment(func(deployment *appsv1.Deployment) error {
+				args := []string{}
+				for _, v := range deployment.Spec.Template.Spec.Containers[0].Args {
+					if strings.Contains(v, "--ingress-class-by-name") &&
+						strings.Contains(v, "--ingress-class=test-new-ingress-class") {
+						continue
+					}
+
+					args = append(args, v)
+				}
+				args = append(args, "--ingress-class=test-new-ingress-class")
+				args = append(args, "--ingress-class-by-name")
+				deployment.Spec.Template.Spec.Containers[0].Args = args
+				_, err := f.KubeClientSet.AppsV1().Deployments(f.Namespace).Update(context.TODO(), deployment, metav1.UpdateOptions{})
+
+				return err
+			})
+			assert.Nil(ginkgo.GinkgoT(), err, "updating ingress controller deployment flags")
+		})
+
+		ginkgo.It("should watch Ingress that uses the class name even if spec is different", func() {
+			validHostClassName := "validhostclassname"
+
+			ing := framework.NewSingleIngress(validHostClassName, "/", validHostClassName, f.Namespace, framework.EchoService, 80, nil)
+			ing.Spec.IngressClassName = &otherIngressClassName
+			f.EnsureIngress(ing)
+
+			validHostClass := "validhostclassspec"
+			ing = framework.NewSingleIngress(validHostClass, "/", validHostClass, f.Namespace, framework.EchoService, 80, nil)
+			f.EnsureIngress(ing)
+
+			invalidHost := "invalidannotation"
+			annotations := map[string]string{
+				ingressclass.IngressKey: "testclass123",
+			}
+			ing = framework.NewSingleIngress(invalidHost, "/", invalidHost, f.Namespace, framework.EchoService, 80, annotations)
+			ing.Spec.IngressClassName = nil
+			f.EnsureIngress(ing)
+
+			f.WaitForNginxConfiguration(func(cfg string) bool {
+				return strings.Contains(cfg, "server_name validhostclassname") &&
+					strings.Contains(cfg, "server_name validhostclassspec") &&
+					!strings.Contains(cfg, "server_name invalidannotation")
+			})
+
+			f.HTTPTestClient().
+				GET("/").
+				WithHeader("Host", validHostClass).
+				Expect().
+				Status(http.StatusOK)
+
+			f.HTTPTestClient().
+				GET("/").
+				WithHeader("Host", validHostClassName).
+				Expect().
+				Status(http.StatusOK)
+
+			f.HTTPTestClient().
+				GET("/").
+				WithHeader("Host", invalidHost).
+				Expect().
+				Status(http.StatusNotFound)
+		})
+
+	})
 })
diff --git a/test/e2e/settings/modsecurity_snippet.go b/test/e2e/settings/modsecurity/modsecurity_snippet.go
similarity index 98%
rename from test/e2e/settings/modsecurity_snippet.go
rename to test/e2e/settings/modsecurity/modsecurity_snippet.go
index c0b962236..f912db984 100644
--- a/test/e2e/settings/modsecurity_snippet.go
+++ b/test/e2e/settings/modsecurity/modsecurity_snippet.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package settings
+package modsecurity
 
 import (
 	"strings"