Repository: kubernetes/ingress-nginx
Pull request #7579 (2021-09-02 09:36:46)

Fix Ingress resources in docs
This PR updates some of the Ingress examples in docs to use the schema of Ingress in version `networking.k8s.io/v1`

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: Hi @ubergesundheit. 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: Welcome @ubergesundheit! <br><br>It looks like this is your first PR to <a href='https://github.com/kubernetes/ingress-nginx'>kubernetes/ingress-nginx</a> 🎉. Please refer to our [pull request process documentation](https://git.k8s.io/community/contributors/guide/pull-requests.md) to help your PR have a smooth ride to approval. <br><br>You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. [Here is the bot commands documentation](https://go.k8s.io/bot-commands). <br><br>You can also check if kubernetes/ingress-nginx has [its own contribution guidelines](https://github.com/kubernetes/ingress-nginx/tree/master/CONTRIBUTING.md). <br><br>You may want to refer to our [testing guide](https://git.k8s.io/community/contributors/devel/sig-testing/testing.md) if you run into trouble with your tests not passing. <br><br>If you are having difficulty getting your pull request seen, please follow the [recommended escalation practices](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#why-is-my-pull-request-not-getting-reviewed). Also, for tips and tricks in the contribution process you may want to read the [Kubernetes contributor cheat sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/README.md). We want to make sure your contribution gets all the attention it needs! <br><br>Thank you, and welcome to Kubernetes. :smiley:
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/7579#" title="Author self-approved">ubergesundheit</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **thockin** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @thockin` 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":["thockin"]} -->
k8s-ci-robot: @ubergesundheit: PR needs rebase.

<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>
ChiefAlexander: Thanks for this change! Can you please rebase your PR.
ChiefAlexander: Thanks please follow the guide that was posted to sign the cla
ubergesundheit: Yeah that’s the problem :( Tried with different browsers and different auth. providers and also with email. There is some problem in the form. I‘m not on my computer atm, will try again later
ubergesundheit: I managed to sign the CLA :) :tada: 
k8s-ci-robot: @ubergesundheit: PR needs rebase.

<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: @ubergesundheit: PR needs rebase.

<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>

Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 63a7bfbf6..f266d0f29 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -43,18 +43,19 @@ kubectl exec -it $POD_NAME -n $POD_NAMESPACE -- /nginx-ingress-controller --vers
 - **OS** (e.g. from /etc/os-release):
 - **Kernel** (e.g. `uname -a`):
 - **Install tools**:
-  - `Please mention how/where was clsuter created like kubeadm/kops/minikube/kind etc. `
+  - `Please mention how/where was the cluster created like kubeadm/kops/minikube/kind etc. `
 - **Basic cluster related info**:
   - `kubectl version`
   - `kubectl get nodes -o wide`
 
 - **How was the ingress-nginx-controller installed**:
-  - If helm was used then please show output of `helm ls -A`
+  - If helm was used then please show output of `helm ls -A | grep -i ingress`
   - If helm was used then please show output of `helm -n <ingresscontrollernamepspace> get values <helmreleasename>`
-  - If helm was not used, then please explain how the ingress-nginx-controller was installed or copy/paste the command used to install the controller below
+  - If helm was not used, then copy/paste the complete precise command used to install the controller, along with the flags and options used
   - if you have more than one instance of the ingress-nginx-controller installed in the same cluster, please provide details for all the instances
 
 - **Current State of the controller**:
+  - `kubectl describe ingressclasses`
   - `kubectl -n <ingresscontrollernamespace> get all -A -o wide`
   - `kubectl -n <ingresscontrollernamespace> describe po <ingresscontrollerpodname>`
   - `kubectl -n <ingresscontrollernamespace> describe svc <ingresscontrollerservicename>`
@@ -104,7 +105,10 @@ echo "
   kind: Ingress
   metadata:
     name: foo-bar
+    annotations:
+      kubernetes.io/ingress.class: nginx
   spec:
+    ingressClassName: nginx # omit this if you're on controller version below 1.0.0
     rules:
     - host: foo.bar
       http:
diff --git a/ISSUE_TRIAGE.md b/ISSUE_TRIAGE.md
index 3a5ba48a9..e6d2c1d8e 100644
--- a/ISSUE_TRIAGE.md
+++ b/ISSUE_TRIAGE.md
@@ -18,9 +18,9 @@ This section describes the different stages of the triage flow for issues.
 New issues come in with the labels `needs-triage` and `needs-priority` and one of: `kind/bug`, `kind/feature` or `kind/support`. Unfortunately there are also some legacy issues that only have a `kind/*` label but neither `needs-triage` nor `needs-priority` . However for every issue that does not have the `triage-accepted` label the following steps have to be done to prepare them for further processing:  
 
 * Filter for issues [without the `triage-accepted`](https://github.com/kubernetes/ingress-nginx/issues?q=is%3Aopen+-label%3Atriage%2Faccepted+is%3Aissue) label.
-* Check if all neccessary information are available. This is basically true, if people filled out the issue template correctly. If neccessary information is missing, ask the author to add the missing information and add the label `triage/needs-information` if not already present. If already present, send the author a friendly reminder to add those. 
+* Check if all necessary information are available. This is basically true, if people filled out the issue template correctly. If necessary information is missing, ask the author to add the missing information and add the label `triage/needs-information` if not already present. If already present, send the author a friendly reminder to add those. 
 * Check if the used versions of ingress-nginx and Kubernetes is supported. Note that [we only support n-3 versions](https://github.com/kubernetes/ingress-nginx#support-versions-table). If the version is not supported, ask the author to upgrade to newer versions and see if the error still persists. 
-* Read through the issue description and comments briefly to understand what the issue is about. Also check if the kind and area is correct, and adjust it if neccessary. If the issue is understandable add the label `triage-accepted`.
+* Read through the issue description and comments briefly to understand what the issue is about. Also check if the kind and area is correct, and adjust it if necessary. If the issue is understandable add the label `triage-accepted`.
 * If at any point you don't know how to proceed with an issue during the triage process, tag one of the [core maintainers](OWNERS_ALIASES) in the issue to raise attention or alternatively come to [this slack channel](https://kubernetes.slack.com/archives/C021E147ZA4) which may be the quicker way as people tend to miss github notifications. 
 
 Note: Issues that are stale for 90 days are being closed automatically. However we could be missing a bug here, so from time to time it makes sense to go over the closed ones and see if there is something important. Use [this filter](https://github.com/kubernetes/ingress-nginx/issues?q=is%3Aclosed+is%3Aissue+label%3Alifecycle%2Frotten+) to find those. 
@@ -28,8 +28,8 @@ Note: Issues that are stale for 90 days are being closed automatically. However
 Who and When?
 * Basically everyone who wants to contribute can do the mentioned steps at any time.
 
-### Issue Priorization
-For all issues, where all neccessary information is available thus triage is accepted, we need to do some priorization: 
+### Issue Prioritization
+For all issues, where all necessary information is available thus triage is accepted, we need to do some prioritization: 
 
 * Go through all issues with label [`triage-accepted`](https://github.com/kubernetes/ingress-nginx/issues?q=is%3Aopen+is%3Aissue+label%3Atriage%2Faccepted+).  
 * Add appropriate priority label: `priority/backlog`, `priority/critical-urgent`, `priority/awaiting-more-evidence`, `priority/important-longterm`, `priority/important-soon` or `good first issue`
@@ -46,14 +46,14 @@ This section describes the different stages of the triage flow for pull requests
 Pull requests come in with the labels `needs-triage`, `needs-priority` and `needs-kind` and one that indicates the size(`size/*`). Unfortunately there are also some legacy pull requests that only have a `size/*` label but neither `needs-triage` nor `needs-priority` . However for every pull request that does not have the `triage-accepted` label the following steps should be done to prepare them for further processing:   
 
 * Filter for pull requests [without the `triage-accepted`](https://github.com/kubernetes/ingress-nginx/pulls?q=is%3Aopen+-label%3Atriage%2Faccepted+is%3Apr) label.
-* Check if the cla is signed and all neccessary information are available. This is basically true, if people filled out the pull request template correctly. If everything is fine add the `triage-accepted` label.
+* Check if the cla is signed and all necessary information are available. This is basically true, if people filled out the pull request template correctly. If everything is fine add the `triage-accepted` label.
 * If at any point you don't know how to proceed with an issue during the triage process, tag one of the [core maintainers](OWNERS_ALIASES) in the issue to raise attention or alternatively come to [this slack channel](https://kubernetes.slack.com/archives/C021E147ZA4) which may be the quicker way as people tend to miss github notifications. 
 
 Who and When?
 * Basically everyone who wants to contribute can do the mentioned steps at any time.
 
-### Pull Request Priorization
-For all pull requests, where all neccessary information is available and cla is signed thus triage is accepted, we need to do some priorization: 
+### Pull Request Prioritization
+For all pull requests, where all necessary information is available and cla is signed thus triage is accepted, we need to do some prioritization: 
 
 * Go through all pull requests with label [`triage-accepted`](https://github.com/kubernetes/ingress-nginx/pulls?q=is%3Aopen+is%3Apr+label%3Atriage%2Faccepted).  
 * Sync the `kind/*` and `priority/*` label from the linked issue for the pull request. If the pull request does not have any issue associated (which normally should not be the case), add an appropriate priority and kind label (one of: `priority/backlog`, `priority/critical-urgent`, `priority/important-longterm`, `priority/important-soon`) 
@@ -79,10 +79,10 @@ Labels are helpful for issues or pull requests to indicate in which lifecycle st
   * [`area/lua`](https://github.com/kubernetes/ingress-nginx/issues?q=is%3Aopen+label%3Aarea%2Flua+is%3Aissue+): Indicates that the issue is related to lua. Add with `/area lua`.
   * [`area/docs`](https://github.com/kubernetes/ingress-nginx/issues?q=is%3Aopen+label%3Aarea%2Fdocs+is%3Aissue): Indicates that the issue is related to documentation. Add with `/area docs` .
 * Priority: 
-  * [`needs-priority`](https://github.com/kubernetes/ingress-nginx/issues?q=is%3Aopen+is%3Aissue+label%3Aneeds-priority): Indicates that the issue has no priorization yet. Automatically added.
+  * [`needs-priority`](https://github.com/kubernetes/ingress-nginx/issues?q=is%3Aopen+is%3Aissue+label%3Aneeds-priority): Indicates that the issue has no prioritization yet. Automatically added.
   * [`priority/critical-urgent`](https://github.com/kubernetes/ingress-nginx/issues?q=is%3Aopen+label%3Apriority%2Fcritical-urgent+is%3Aissue+): indicates that the issue has highest priority. Add with `/priority critical-urgent`.
   * [`priority/important-soon`](https://github.com/kubernetes/ingress-nginx/issues?q=is%3Aopen+label%3Apriority%2Fimportant-soon+is%3Aissue+): indicates that the issue should be worked on either currently soon, ideally in time for the next release. Add with `/priority important-soon`.
-  * [`priority/important-longterm`](https://github.com/kubernetes/ingress-nginx/issues?q=is%3Aopen+label%3Apriority%2Fimportant-longterm+is%3Aissue+): indicates that the issue is not important for now, but should be worked on in one of the upcomming releases. Add with `/priority important-longterm`.
+  * [`priority/important-longterm`](https://github.com/kubernetes/ingress-nginx/issues?q=is%3Aopen+label%3Apriority%2Fimportant-longterm+is%3Aissue+): indicates that the issue is not important for now, but should be worked on in one of the upcoming releases. Add with `/priority important-longterm`.
   * [`priority/backlog`](https://github.com/kubernetes/ingress-nginx/issues?q=is%3Aopen+label%3Apriority%2Fbacklog+is%3Aissue+): Indicates that the issue has the lowest priority. Add with `/priority backlog`.
 * Other: 
   * [`help wanted`](https://github.com/kubernetes/ingress-nginx/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22): indicates that the issue needs help from a contributor. Add with `/help`.
diff --git a/build/build-plugin.sh b/build/build-plugin.sh
index 578957eb4..93b6efbdf 100755
--- a/build/build-plugin.sh
+++ b/build/build-plugin.sh
@@ -57,7 +57,7 @@ function build_for_arch(){
     -trimpath -ldflags="-buildid= -w -s" \
       -X ${PKG}/version.RELEASE=${TAG} \
       -X ${PKG}/version.COMMIT=${COMMIT_SHA} \
-      -X ${PKG}/version.REPO=${REPO_INFO}" \
+      -X ${PKG}/version.REPO=${REPO_INFO} \
     -o "${release}/kubectl-ingress_nginx${extension}" "${PKG}/cmd/plugin"
 
     cp LICENSE ${release}
diff --git a/build/dev-env.sh b/build/dev-env.sh
index b089de206..d98f0bfbd 100755
--- a/build/dev-env.sh
+++ b/build/dev-env.sh
@@ -61,7 +61,7 @@ echo "[dev-env] building image"
 make build image
 docker tag "${REGISTRY}/controller:${TAG}" "${DEV_IMAGE}"
 
-export K8S_VERSION=${K8S_VERSION:-v1.20.2@sha256:8f7ea6e7642c0da54f04a7ee10431549c0257315b3a634f6ef2fecaaedb19bab}
+export K8S_VERSION=${K8S_VERSION:-v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6}
 
 KIND_CLUSTER_NAME="ingress-nginx-dev"
 
diff --git a/charts/ingress-nginx/README.md b/charts/ingress-nginx/README.md
index fecbbcde2..5e5c8b235 100644
--- a/charts/ingress-nginx/README.md
+++ b/charts/ingress-nginx/README.md
@@ -2,13 +2,14 @@
 
 [ingress-nginx](https://github.com/kubernetes/ingress-nginx) Ingress controller for Kubernetes using NGINX as a reverse proxy and load balancer
 
-To use, add the `kubernetes.io/ingress.class: nginx` annotation to your Ingress resources.
+To use, add `ingressClassName: nginx` spec field or the `kubernetes.io/ingress.class: nginx` annotation to your Ingress resources.
 
 This chart bootstraps an ingress-nginx deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.
 
 ## Prerequisites
 
-- Kubernetes v1.16+
+- Chart version 3.x.x: Kubernetes v1.16+
+- Chart version 4.x.x and above: Kubernetes v1.19+
 
 ## Get Repo Info
 
@@ -177,8 +178,8 @@ controller:
         networking.gke.io/load-balancer-type: "Internal"
         # For earlier versions
         # cloud.google.com/load-balancer-type: "Internal"
-        
-        # Any other annotation can be declared here. 
+
+        # Any other annotation can be declared here.
 ```
 
 Example for Azure:
diff --git a/charts/ingress-nginx/templates/NOTES.txt b/charts/ingress-nginx/templates/NOTES.txt
index 2dbf14f21..c10ab03ed 100644
--- a/charts/ingress-nginx/templates/NOTES.txt
+++ b/charts/ingress-nginx/templates/NOTES.txt
@@ -29,23 +29,30 @@ Get the application URL by running these commands:
 
 An example Ingress that makes use of the controller:
 
+{{- $isV1 := semverCompare ">=1" .Chart.AppVersion}}
   apiVersion: networking.k8s.io/v1
   kind: Ingress
   metadata:
     name: example
     namespace: foo
+    {{- if eq $isV1 false }}
+    annotations:
+      kubernetes.io/ingress.class: {{ .Values.controller.ingressClass }}
+    {{- end }}
   spec:
-    ingressClassName: example-class
+    {{- if $isV1 }}
+    ingressClassName: {{ .Values.controller.ingressClassResource.name }}
+    {{- end }}
     rules:
       - host: www.example.com
         http:
           paths:
-            - path: /
-              pathType: Prefix
-              backend:
+            - backend:
                 service:
                   name: exampleService
-                  port: 80
+                  port:
+                    number: 80
+              path: /
     # This section is only required if TLS is to be enabled for the Ingress
     tls:
       - hosts:
diff --git a/charts/ingress-nginx/templates/controller-configmap.yaml b/charts/ingress-nginx/templates/controller-configmap.yaml
index 697389207..5d618420f 100644
--- a/charts/ingress-nginx/templates/controller-configmap.yaml
+++ b/charts/ingress-nginx/templates/controller-configmap.yaml
@@ -21,6 +21,6 @@ data:
   ssl-dh-param: {{ printf "%s/%s" .Release.Namespace (include "ingress-nginx.controller.fullname" .) }}
 {{- end }}
 {{- range $key, $value := .Values.controller.config }}
-    {{ $key | nindent 2 }}: {{ $value | quote }}
+    {{- $key | nindent 2 }}: {{ $value | quote }}
 {{- end }}
 
diff --git a/cmd/nginx/main.go b/cmd/nginx/main.go
index 69d12d05e..fb87ad84a 100644
--- a/cmd/nginx/main.go
+++ b/cmd/nginx/main.go
@@ -138,7 +138,9 @@ func main() {
 			klog.Fatalf("Error creating prometheus collector:  %v", err)
 		}
 	}
-	mc.Start()
+	// Pass the ValidationWebhook status to determine if we need to start the collector
+	// for the admissionWebhook
+	mc.Start(conf.ValidationWebhook)
 
 	if conf.EnableProfiling {
 		go registerProfiler()
diff --git a/docs/deploy/index.md b/docs/deploy/index.md
index f5074b078..4dea7d985 100644
--- a/docs/deploy/index.md
+++ b/docs/deploy/index.md
@@ -1,37 +1,21 @@
 # Installation Guide
 
-!!! attention
-    The default configuration watches Ingress object from **all namespaces**.
-
-    To change this behavior use the flag `--watch-namespace` to limit the scope to a particular namespace.
-
-!!! warning
-    If multiple Ingresses define paths for the same host, the ingress controller **merges the definitions**.
-
-!!! danger
-    The [admission webhook](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/) requires connectivity between Kubernetes API server and the ingress controller.
-
-    In case [Network policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/) or additional firewalls, please allow access to port `8443`.
+There are multiple ways to install the NGINX ingress controller:
+- with [Helm](https://helm.sh), using the project repository chart;
+- with `kubectl apply`, using YAML manifests;
+- with specific addons (e.g. for [minikube](#minikube) or [MicroK8s](#microk8s)).
 
-!!! attention
-    The first time the ingress controller starts, two [Jobs](https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/) create the SSL Certificate used by the admission webhook.
-    For this reason, there is an initial delay of up to two minutes until it is possible to create and validate Ingress definitions. 
-
-You can wait until it is ready to run the next command:
-
-```yaml
- kubectl wait --namespace ingress-nginx \
-  --for=condition=ready pod \
-  --selector=app.kubernetes.io/component=controller \
-  --timeout=120s
-```
+On most Kubernetes clusters, the ingress controller will work without requiring any extra configuration. If you want to get started as fast as possible, you can check the [quick start](#quick-start) instructions. However, in many environments, you can improve the performance or get better logs by enabling extra features. we recommend that you check the [environment-specific instructions](#environment-specific-instructions) for details about optimizing the ingress controller for your particular environment or cloud provider.
 
 ## Contents
 
-- [Provider Specific Steps](#provider-specific-steps)
+<!-- Quick tip: run `grep '^##' index.md` to check that the table of contents is up to date. -->
+
+- [Quick start](#quick-start)
+- [Environment-specific instructions](#environment-specific-instructions)
   - [Docker Desktop](#docker-desktop)
   - [minikube](#minikube)
-  - [microk8s](#microk8s)
+  - [MicroK8s](#microk8s)
   - [AWS](#aws)
   - [GCE - GKE](#gce-gke)
   - [Azure](#azure)
@@ -40,18 +24,26 @@ You can wait until it is ready to run the next command:
   - [Exoscale](#exoscale)
   - [Oracle Cloud Infrastructure](#oracle-cloud-infrastructure)
   - [Bare-metal](#bare-metal)
-  - [Verify installation](#verify-installation)
-  - [Detect installed version](#detect-installed-version)
-- [Using Helm](#using-helm)
+- [Miscellaneous](#miscellaneous)
 
-### Provider Specific Steps
+## Quick start
 
-#### Docker Desktop
+You can deploy the ingress controller with the following command:
 
-Kubernetes is available in Docker Desktop
+```console
+helm upgrade --install ingress-nginx ingress-nginx \
+  --repo https://kubernetes.github.io/ingress-nginx \
+  --namespace ingress-nginx --create-namespace
+```
 
-- Mac, from [version 18.06.0-ce](https://docs.docker.com/docker-for-mac/release-notes/#stable-releases-of-2018)
-- Windows, from [version 18.06.0-ce](https://docs.docker.com/docker-for-windows/release-notes/#docker-community-edition-18060-ce-win70-2018-07-25)
+It will install the controller in the `ingress-nginx` namespace, creating that namespace if it doesn't already exist.
+
+!!! info
+    This command is *idempotent*:
+    - if the ingress controller is not installed, it will install it,
+    - if the ingress controller is already installed, it will upgrade it.
+
+This requires Helm version 3. If you prefer to use a YAML manifest, you can run the following command instead:
 
 !!! attention
     Before running the command at your terminal, make sure Kubernetes is enabled at Docker settings
@@ -60,23 +52,112 @@ Kubernetes is available in Docker Desktop
 kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.0.4/deploy/static/provider/cloud/deploy.yaml
 ```
 
+!!! info
+    The YAML manifest in the command above was generated with `helm template`, so you will end up with almost the same resources as if you had used Helm to install the controller.
+
+If you are running an old version of Kubernetes (1.18 or earlier), please read
+[this paragraph](#running-on-Kubernetes-versions-older-than-1.19) for specific instructions.
+
+### Pre-flight check
+
+A few pods should start in the `ingress-nginx` namespace:
+
+```console
+kubectl get pods --namespace=ingress-nginx
+```
+
+After a while, they should all be running. The following command will wait for the ingress controller pod to be up, running, and ready:
+
+```console
+kubectl wait --namespace ingress-nginx \
+  --for=condition=ready pod \
+  --selector=app.kubernetes.io/component=controller \
+  --timeout=120s
+```
+
+### Local testing
+
+Let's create a simple web server and the associated service:
+
+```console
+kubectl create deployment demo --image=httpd --port=80
+kubectl expose deployment demo
+```
+
+Then create an ingress resource. The following example uses an host that maps to `localhost`:
+
+```console
+kubectl create ingress demo-localhost --class=nginx \
+  --rule=demo.localdev.me/*=demo:80
+```
+
+Now, forward a local port to the ingress controller:
+
+```console
+kubectl port-forward --namespace=ingress-nginx service/ingress-nginx-controller 8080:80
+```
+
+At this point, if you access http://demo.localdev.me:8080/, you should see an HTML page telling you "It works!".
+
+### Online testing
+
+If your Kubernetes cluster is a "real" cluster that supports services of type `LoadBalancer`, it will have allocated an external IP address or FQDN to the ingress controller.
+
+You can see that IP address or FQDN with the following command:
+
+```console
+kubectl get service ingress-nginx-controller --namespace=ingress-nginx
+```
+
+Set up a DNS record pointing to that IP address or FQDN; then create an ingress resource. The following example assumes that you have set up a DNS record for `www.demo.io`:
+
+```console
+kubectl create ingress demo --class=nginx \
+  --rule=www.demo.io/*=demo:80
+```
+
+You should then be able to see the "It works!" page when you connect to http://www.demo.io/. Congratulations, you are serving a public web site hosted on a Kubernetes cluster! 🎉
+
+## Environment-specific instructions
+
+### Local development clusters
+
 #### minikube
 
-For standard usage:
+The ingress controller can be installed through minikube's addons system:
 
 ```console
 minikube addons enable ingress
 ```
 
-#### microk8s
+#### MicroK8s
 
-For standard usage:
+The ingress controller can be installed through MicroK8s's addons system:
 
 ```console
 microk8s enable ingress
 ```
 
-Please check the microk8s [documentation page](https://microk8s.io/docs/addon-ingress)
+Please check the MicroK8s [documentation page](https://microk8s.io/docs/addon-ingress) for details.
+
+#### Docker Desktop
+
+Kubernetes is available in Docker Desktop:
+
+- Mac, from [version 18.06.0-ce](https://docs.docker.com/docker-for-mac/release-notes/#stable-releases-of-2018)
+- Windows, from [version 18.06.0-ce](https://docs.docker.com/docker-for-windows/release-notes/#docker-community-edition-18060-ce-win70-2018-07-25)
+
+The ingress controller can be installed on Docker Desktop using the default [quick start](#quick-start) instructions.
+
+On most systems, if you don't have any other service of type `LoadBalancer` bound to port 80, the ingress controller will be assigned the `EXTERNAL-IP` of `localhost`, which means that it will be reachable on localhost:80. If that doesn't work, you might have to fall back to the `kubectl port-forward` method described in the [local testing section](#local-testing).
+
+### Cloud deployments
+
+If the load balancers of your cloud provider do active healthchecks on their backends (most do), you can change the `externalTrafficPolicy` of the ingress controller Service to `Local` (instead of the default `Cluster`) to save an extra hop in some cases. If you're installing with Helm, this can be done by adding `--set controller.service.externalTrafficPolicy=Local` to the `helm install` or `helm upgrade` command.
+
+Furthermore, if the load balancers of your cloud provider support the PROXY protocol, you can enable it, and it will let the ingress controller see the real IP address of the clients. Otherwise, it will generally see the IP address of the upstream load balancer. This must be done both in the ingress controller (with e.g. `--set controller.config.use-proxy-protocol=true`) and in the cloud provider's load balancer configuration to function correctly.
+
+In the following sections, we provide YAML manifests that enable these options when possible, using the specific options of various cloud providers.
 
 #### AWS
 
@@ -189,7 +270,7 @@ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/cont
 
 A [complete list of available annotations for Oracle Cloud Infrastructure](https://github.com/oracle/oci-cloud-controller-manager/blob/master/docs/load-balancer-annotations.md) can be found in the [OCI Cloud Controller Manager](https://github.com/oracle/oci-cloud-controller-manager) documentation.
 
-#### Bare-metal
+### Bare metal clusters
 
 Using [NodePort](https://kubernetes.io/docs/concepts/services-networking/service/#nodeport):
 
@@ -203,50 +284,58 @@ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/cont
 !!! info
     For extended notes regarding deployments on bare-metal, see [Bare-metal considerations](./baremetal.md).
 
-### Verify installation
+## Miscellaneous
+
+### Checking ingress controller version
 
-To check if the ingress controller pods have started, run the following command:
+Run `nginx-ingress-controller --version` within the pod, for instance with `kubectl exec`:
 
 ```console
-kubectl get pods -n ingress-nginx \
-  -l app.kubernetes.io/name=ingress-nginx --watch
+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 name)
+kubectl exec $POD_NAME -n $POD_NAMESPACE -- /nginx-ingress-controller --version
 ```
 
-Once the ingress controller pods are running, you can cancel the command typing `Ctrl+C`.
+### Scope
 
-Now, you are ready to create your first ingress.
+By default, the controller watches Ingress objects from all namespaces. If you want to change this behavior, use the flag `--watch-namespace` or check the Helm chart value `controller.scope` to limit the controller to a single namespace.
 
-### Detect installed version
+See also [“How to easily install multiple instances of the Ingress NGINX controller in the same cluster”](https://kubernetes.github.io/ingress-nginx/#how-to-easily-install-multiple-instances-of-the-ingress-nginx-controller-in-the-same-cluster) for more details.
 
-To detect which version of the ingress controller is running, exec into the pod and run `nginx-ingress-controller --version`.
+### Webhook network access
 
-```console
-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
-```
+!!! warning
+    The controller uses an [admission webhook](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/) to validate Ingress definitions. Make sure that you don't have [Network policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/) or additional firewalls preventing connections from the API server to the `ingress-nginx-controller-admission` service.
 
-## Using Helm
+### Certificate generation
 
 !!! attention
-    Only Helm v3 is supported
+    The first time the ingress controller starts, two [Jobs](https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/) create the SSL Certificate used by the admission webhook.
 
-NGINX Ingress controller can be installed via [Helm](https://helm.sh/) using the chart from the project repository.
-To install the chart with the release name `ingress-nginx`:
+THis can cause an initial delay of up to two minutes until it is possible to create and validate Ingress definitions.
 
-```console
-helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
-helm repo update
+You can wait until it is ready to run the next command:
 
-helm install ingress-nginx ingress-nginx/ingress-nginx
+```yaml
+ kubectl wait --namespace ingress-nginx \
+  --for=condition=ready pod \
+  --selector=app.kubernetes.io/component=controller \
+  --timeout=120s
 ```
 
-[For multiple NGINX Ingress controllers](https://kubernetes.github.io/ingress-nginx/#how-to-easily-install-multiple-instances-of-the-ingress-nginx-controller-in-the-same-cluster)
+### Running on Kubernetes versions older than 1.19
 
-## Detect installed version:
+Ingress resources evolved over time. They started with `apiVersion: extensions/v1beta1`, then moved to `apiVersion: networking.k8s.io/v1beta1` and more recently to `apiVersion: networking.k8s.io/v1`.
 
-```console
-POD_NAME=$(kubectl get pods -l app.kubernetes.io/name=ingress-nginx -o jsonpath='{.items[0].metadata.name}')
-kubectl exec -it $POD_NAME -- /nginx-ingress-controller --version
-```
+Here is how these Ingress versions are supported in Kubernetes:
+- before Kubernetes 1.19, only `v1beta1` Ingress resources are supported
+- from Kubernetes 1.19 to 1.21, both `v1beta1` and `v1` Ingress resources are supported
+- in Kubernetes 1.22 and above, only `v1` Ingress resources are supported
+
+And here is how these Ingress versions are supported in NGINX Ingress Controller:
+- before version 1.0, only `v1beta1` Ingress resources are supported
+- in version 1.0 and above, only `v1` Ingress resources are
+
+As a result, if you're running Kubernetes 1.19 or later, you should be able to use the latest version of the NGINX Ingress Controller; but if you're using an old version of Kubernetes (1.18 or earlier) you will have to use version 0.X of the NGINX Ingress Controller (e.g. version 0.49).
+
+The Helm chart of the NGINX Ingress Controller switched to version 1 in version 4 of the chart. In other words, if you're running Kubernetes 1.19 or earlier, you should use version 3.X of the chart (this can be done by adding `--version='<4'` to the `helm install` command).
diff --git a/docs/examples/affinity/cookie/ingress-samesite.yaml b/docs/examples/affinity/cookie/ingress-samesite.yaml
index d03a25e39..2f7022ef7 100644
--- a/docs/examples/affinity/cookie/ingress-samesite.yaml
+++ b/docs/examples/affinity/cookie/ingress-samesite.yaml
@@ -11,6 +11,7 @@ metadata:
     nginx.ingress.kubernetes.io/session-cookie-samesite: "None"
     nginx.ingress.kubernetes.io/session-cookie-conditional-samesite-none: "true" # omits SameSite=None for older browsers which reject cookies with SameSite=None
 spec:
+  ingressClassName: nginx
   rules:
   - host: stickyingress-samesite-none.example.com
     http:
@@ -34,6 +35,7 @@ metadata:
     nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
     nginx.ingress.kubernetes.io/session-cookie-samesite: "Strict"
 spec:
+  ingressClassName: nginx
   rules:
   - host: stickyingress-samesite-strict.example.com
     http:
@@ -44,4 +46,4 @@ spec:
           service:
             name: http-svc
             port:
-              number: 80
\ No newline at end of file
+              number: 80
diff --git a/docs/examples/affinity/cookie/ingress.yaml b/docs/examples/affinity/cookie/ingress.yaml
index d3053021e..4ca9fbc09 100644
--- a/docs/examples/affinity/cookie/ingress.yaml
+++ b/docs/examples/affinity/cookie/ingress.yaml
@@ -9,6 +9,7 @@ metadata:
     nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
 
 spec:
+  ingressClassName: nginx
   rules:
   - host: stickyingress.example.com
     http:
diff --git a/docs/examples/auth/basic/README.md b/docs/examples/auth/basic/README.md
index d3d255afb..ffd8495f3 100644
--- a/docs/examples/auth/basic/README.md
+++ b/docs/examples/auth/basic/README.md
@@ -42,6 +42,7 @@ metadata:
     # message to display with an appropriate context why the authentication is required
     nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - foo'
 spec:
+  ingressClassName: nginx
   rules:
   - host: foo.bar.com
     http:
diff --git a/docs/examples/auth/client-certs/ingress.yaml b/docs/examples/auth/client-certs/ingress.yaml
index 90ff87514..0cd56fcbf 100644
--- a/docs/examples/auth/client-certs/ingress.yaml
+++ b/docs/examples/auth/client-certs/ingress.yaml
@@ -15,6 +15,7 @@ metadata:
   name: nginx-test
   namespace: default
 spec:
+  ingressClassName: nginx
   rules:
   - host: mydomain.com
     http:
diff --git a/docs/examples/auth/external-auth/ingress.yaml b/docs/examples/auth/external-auth/ingress.yaml
index df80cbb25..1aa2f36e9 100644
--- a/docs/examples/auth/external-auth/ingress.yaml
+++ b/docs/examples/auth/external-auth/ingress.yaml
@@ -5,6 +5,7 @@ metadata:
     nginx.ingress.kubernetes.io/auth-url: "https://httpbin.org/basic-auth/user/passwd"
   name: external-auth
 spec:
+  ingressClassName: nginx
   rules:
   - host: external-auth-01.sample.com
     http:
@@ -15,4 +16,4 @@ spec:
           service:
             name: http-svc
             port:
-              number: 80
\ No newline at end of file
+              number: 80
diff --git a/docs/examples/auth/oauth-external-auth/dashboard-ingress.yaml b/docs/examples/auth/oauth-external-auth/dashboard-ingress.yaml
index 4c0a2d373..198a165f4 100644
--- a/docs/examples/auth/oauth-external-auth/dashboard-ingress.yaml
+++ b/docs/examples/auth/oauth-external-auth/dashboard-ingress.yaml
@@ -7,6 +7,7 @@ metadata:
   name: external-auth-oauth2
   namespace: kube-system
 spec:
+  ingressClassName: nginx
   rules:
   - host: __INGRESS_HOST__
     http:
@@ -26,6 +27,7 @@ metadata:
   name: oauth2-proxy
   namespace: kube-system
 spec:
+  ingressClassName: nginx
   rules:
   - host: __INGRESS_HOST__
     http:
diff --git a/docs/examples/chashsubset/deployment.yaml b/docs/examples/chashsubset/deployment.yaml
index 867cd01c0..0ac13fcce 100644
--- a/docs/examples/chashsubset/deployment.yaml
+++ b/docs/examples/chashsubset/deployment.yaml
@@ -64,6 +64,7 @@ metadata:
   name: nginxhello-ingress
   namespace: default
 spec:
+  ingressClassName: nginx
   rules:
   - host: foo.bar.com
     http:
@@ -75,4 +76,3 @@ spec:
               name: nginxhello
               port:
                 number: 80
-
diff --git a/docs/examples/customization/configuration-snippets/ingress.yaml b/docs/examples/customization/configuration-snippets/ingress.yaml
index aaf31f0bb..1f0c4f24a 100644
--- a/docs/examples/customization/configuration-snippets/ingress.yaml
+++ b/docs/examples/customization/configuration-snippets/ingress.yaml
@@ -6,6 +6,7 @@ metadata:
     nginx.ingress.kubernetes.io/configuration-snippet: |
       more_set_headers "Request-Id: $req_id";
 spec:
+  ingressClassName: nginx
   rules:
   - host: custom.configuration.com
     http:
diff --git a/docs/examples/customization/external-auth-headers/deploy/echo-service.yaml b/docs/examples/customization/external-auth-headers/deploy/echo-service.yaml
index 3a80ae834..9881ac8d0 100644
--- a/docs/examples/customization/external-auth-headers/deploy/echo-service.yaml
+++ b/docs/examples/customization/external-auth-headers/deploy/echo-service.yaml
@@ -52,6 +52,7 @@ metadata:
     nginx.ingress.kubernetes.io/auth-response-headers: UserID, UserRole
   namespace: default
 spec:
+  ingressClassName: nginx
   rules:
   - host: public-demo-echo-service.kube.local
     http:
@@ -73,6 +74,7 @@ metadata:
     nginx.ingress.kubernetes.io/auth-response-headers: UserID, UserRole
   namespace: default
 spec:
+  ingressClassName: nginx
   rules:
   - host: secure-demo-echo-service.kube.local
     http:
diff --git a/docs/examples/docker-registry/ingress-with-tls.yaml b/docs/examples/docker-registry/ingress-with-tls.yaml
index bef35c50b..1bdd9ed7a 100644
--- a/docs/examples/docker-registry/ingress-with-tls.yaml
+++ b/docs/examples/docker-registry/ingress-with-tls.yaml
@@ -9,6 +9,7 @@ metadata:
   name: docker-registry
   namespace: docker-registry
 spec:
+  ingressClassName: nginx
   tls:
   - hosts:
     - registry.<your domain>
@@ -23,4 +24,4 @@ spec:
           service:
             name: docker-registry
             port:
-              number: 5000
\ No newline at end of file
+              number: 5000
diff --git a/docs/examples/docker-registry/ingress-without-tls.yaml b/docs/examples/docker-registry/ingress-without-tls.yaml
index f64cbb295..d0ffc8baf 100644
--- a/docs/examples/docker-registry/ingress-without-tls.yaml
+++ b/docs/examples/docker-registry/ingress-without-tls.yaml
@@ -8,6 +8,7 @@ metadata:
   name: docker-registry
   namespace: docker-registry
 spec:
+  ingressClassName: nginx
   rules:
   - host: registry.<your domain>
     http:
@@ -18,4 +19,4 @@ spec:
           service:
             name: docker-registry
             port:
-              number: 5000
+              number: 5000
\ No newline at end of file
diff --git a/docs/examples/grpc/README.md b/docs/examples/grpc/README.md
index a20c8c7dd..a738450a9 100644
--- a/docs/examples/grpc/README.md
+++ b/docs/examples/grpc/README.md
@@ -16,7 +16,7 @@ This example demonstrates how to route traffic to a gRPC service through the ngi
   ```
   $ kubectl get po -A -o wide | grep go-grpc-greeter-server
   ```
-- If you have a gRPC app deployed in your cluster, then skip further notes in this Step 1, and continue from Step 2 below. 
+- If you have a gRPC app deployed in your cluster, then skip further notes in this Step 1, and continue from Step 2 below.
 
 - As an example gRPC application, we can use this app <https://github.com/grpc/grpc-go/blob/91e0aeb192456225adf27966d04ada4cf8599915/examples/features/reflection/server/main.go> .
 
@@ -94,12 +94,12 @@ This example demonstrates how to route traffic to a gRPC service through the ngi
   kind: Ingress
   metadata:
     annotations:
-      kubernetes.io/ingress.class: "nginx"
       nginx.ingress.kubernetes.io/ssl-redirect: "true"
       nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
     name: fortune-ingress
     namespace: default
   spec:
+    ingressClassName: nginx
     rules:
     - host: grpctest.dev.mydomain.com
       http:
diff --git a/docs/examples/index.md b/docs/examples/index.md
index 04f42ea60..8a5fd5f51 100644
--- a/docs/examples/index.md
+++ b/docs/examples/index.md
@@ -1,8 +1,12 @@
 # Ingress examples
 
-This directory contains a catalog of examples on how to run, configure and scale Ingress.  
+This directory contains a catalog of examples on how to run, configure and scale Ingress.
 Please review the [prerequisites](PREREQUISITES.md) before trying them.
 
+The examples on these pages include the `spec.ingressClassName` field which replaces the deprecated `kubernetes.io/ingress.class: nginx` annotation. Users of ingress-nginx < 1.0.0 (Helm chart < 4.0.0) should use the [legacy documentation](https://github.com/kubernetes/ingress-nginx/tree/legacy/docs/examples).
+
+For more information, check out the [Migration to apiVersion networking.k8s.io/v1](../#faq-migration-to-apiversion-networkingk8siov1) guide.
+
 Category | Name | Description | Complexity Level
 ---------| ---- | ----------- | ----------------
 Apps | [Docker Registry](docker-registry/README.md) | TODO | TODO
@@ -14,7 +18,7 @@ Customization | [Configuration snippets](customization/configuration-snippets/RE
 Customization | [Custom configuration](customization/custom-configuration/README.md) | TODO | TODO
 Customization | [Custom DH parameters for perfect forward secrecy](customization/ssl-dh-param/README.md) | TODO | TODO
 Customization | [Custom errors](customization/custom-errors/README.md) | serve custom error pages from the default backend | Intermediate
-Customization | [Custom headers](customization/custom-headers/README.md) | set custom headers before sending traffic to backends  | Advanced
+Customization | [Custom headers](customization/custom-headers/README.md) | set custom headers before sending traffic to backends | Advanced
 Customization | [External authentication with response header propagation](customization/external-auth-headers/README.md) | TODO | TODO
 Customization | [Sysctl tuning](customization/sysctl/README.md) | TODO | TODO
 Features | [Rewrite](rewrite/README.md) | TODO | TODO
diff --git a/docs/examples/multi-tls/multi-tls.yaml b/docs/examples/multi-tls/multi-tls.yaml
index aa2cf54d6..3612ab6d7 100644
--- a/docs/examples/multi-tls/multi-tls.yaml
+++ b/docs/examples/multi-tls/multi-tls.yaml
@@ -98,6 +98,7 @@ metadata:
   name: foo-tls
   namespace: default
 spec:
+  ingressClassName: nginx
   tls:
   - hosts:
     - foo.bar.com
diff --git a/docs/examples/rewrite/README.md b/docs/examples/rewrite/README.md
index 3c48b100f..fbe9f30c7 100644
--- a/docs/examples/rewrite/README.md
+++ b/docs/examples/rewrite/README.md
@@ -42,6 +42,7 @@ metadata:
   name: rewrite
   namespace: default
 spec:
+  ingressClassName: nginx
   rules:
   - host: rewrite.bar.com
     http:
@@ -77,6 +78,7 @@ metadata:
   name: approot
   namespace: default
 spec:
+  ingressClassName: nginx
   rules:
   - host: approot.bar.com
     http:
diff --git a/docs/examples/static-ip/nginx-ingress.yaml b/docs/examples/static-ip/nginx-ingress.yaml
index 740f46e89..5c8a3c920 100644
--- a/docs/examples/static-ip/nginx-ingress.yaml
+++ b/docs/examples/static-ip/nginx-ingress.yaml
@@ -3,6 +3,7 @@ kind: Ingress
 metadata:
   name: ingress-nginx
 spec:
+  ingressClassName: nginx
   tls:
   # This assumes tls-secret exists.
   - secretName: tls-secret
diff --git a/docs/examples/tls-termination/README.md b/docs/examples/tls-termination/README.md
index 2df767e26..f4fe2e81d 100644
--- a/docs/examples/tls-termination/README.md
+++ b/docs/examples/tls-termination/README.md
@@ -22,6 +22,7 @@ spec:
       # This assumes tls-secret exists and the SSL
       # certificate contains a CN for foo.bar.com
       secretName: tls-secret
+  ingressClassName: nginx
   rules:
     - host: foo.bar.com
       http:
@@ -32,7 +33,7 @@ spec:
             # This assumes http-svc exists and routes to healthy endpoints
             service:
               name: http-svc
-              port: 
+              port:
                 number: 80
 ```
 
diff --git a/docs/examples/tls-termination/ingress.yaml b/docs/examples/tls-termination/ingress.yaml
index debaa45b2..890247fd3 100644
--- a/docs/examples/tls-termination/ingress.yaml
+++ b/docs/examples/tls-termination/ingress.yaml
@@ -3,6 +3,7 @@ kind: Ingress
 metadata:
   name: nginx-test
 spec:
+  ingressClassName: nginx
   tls:
     - hosts:
       - foo.bar.com
diff --git a/docs/user-guide/basic-usage.md b/docs/user-guide/basic-usage.md
index 64c81148b..cbb7004c7 100644
--- a/docs/user-guide/basic-usage.md
+++ b/docs/user-guide/basic-usage.md
@@ -13,10 +13,8 @@ apiVersion: networking.k8s.io/v1
 kind: Ingress
 metadata:
   name: ingress-myservicea
-  annotations:
-    # use the shared ingress-nginx
-    kubernetes.io/ingress.class: "nginx"
 spec:
+  ingressClassName: nginx
   rules:
   - host: myservicea.foo.org
     http:
@@ -67,7 +65,7 @@ spec:
         backend:
           service:
             name: myservicea
-            port: 
+            port:
               number: 80
   ingressClassName: nginx
 ---
@@ -85,7 +83,7 @@ spec:
         backend:
           service:
             name: myserviceb
-            port: 
+            port:
               number: 80
   ingressClassName: nginx
 ```
diff --git a/docs/user-guide/fcgi-services.md b/docs/user-guide/fcgi-services.md
index 03afc89d1..becb4819f 100644
--- a/docs/user-guide/fcgi-services.md
+++ b/docs/user-guide/fcgi-services.md
@@ -64,12 +64,12 @@ apiVersion: networking.k8s.io/v1
 kind: Ingress
 metadata:
   annotations:
-    kubernetes.io/ingress.class: "nginx"
     nginx.ingress.kubernetes.io/backend-protocol: "FCGI"
     nginx.ingress.kubernetes.io/fastcgi-index: "index.php"
     nginx.ingress.kubernetes.io/fastcgi-params-configmap: "example-cm"
   name: example-app
 spec:
+  ingressClassName: nginx
   rules:
   - host: app.example.com
     http:
diff --git a/docs/user-guide/ingress-path-matching.md b/docs/user-guide/ingress-path-matching.md
index e5e0d9a50..321ddfa8e 100644
--- a/docs/user-guide/ingress-path-matching.md
+++ b/docs/user-guide/ingress-path-matching.md
@@ -22,6 +22,7 @@ metadata:
   annotations:
     nginx.ingress.kubernetes.io/use-regex: "true"
 spec:
+  ingressClassName: nginx
   rules:
   - host: test.com
     http:
@@ -59,6 +60,7 @@ kind: Ingress
 metadata:
   name: test-ingress-1
 spec:
+  ingressClassName: nginx
   rules:
   - host: test.com
     http:
@@ -87,6 +89,7 @@ metadata:
   annotations:
     nginx.ingress.kubernetes.io/rewrite-target: /$1
 spec:
+  ingressClassName: nginx
   rules:
   - host: test.com
     http:
@@ -144,6 +147,7 @@ metadata:
   annotations:
     nginx.ingress.kubernetes.io/use-regex: "true"
 spec:
+  ingressClassName: nginx
   rules:
   - host: test.com
     http:
diff --git a/docs/user-guide/nginx-configuration/annotations.md b/docs/user-guide/nginx-configuration/annotations.md
index ca515f9e2..19bd3947f 100755
--- a/docs/user-guide/nginx-configuration/annotations.md
+++ b/docs/user-guide/nginx-configuration/annotations.md
@@ -354,10 +354,13 @@ CORS can be controlled with the following annotations:
 
 * `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`
+    This is a multi-valued field, separated by ','. It must follow this 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"`
+    - Example: `nginx.ingress.kubernetes.io/cors-allow-origin: "https://origin-site.com:4443, http://origin-site.com, https://example.org:1199"`
+
+    It also supports single level wildcard subdomains and follows this format: `http(s)://*.foo.bar`, `http(s)://*.bar.foo:8080` or `http(s)://*.abc.bar.foo:9000`
+    - Example: `nginx.ingress.kubernetes.io/cors-allow-origin: "https://*.origin-site.com:4443, http://*.origin-site.com, https://example.org:1199"`
 
 * `nginx.ingress.kubernetes.io/cors-allow-credentials`: Controls if credentials can be passed during CORS operations.
 
diff --git a/docs/user-guide/nginx-configuration/configmap.md b/docs/user-guide/nginx-configuration/configmap.md
index 4fdda21ba..fc093c4af 100755
--- a/docs/user-guide/nginx-configuration/configmap.md
+++ b/docs/user-guide/nginx-configuration/configmap.md
@@ -98,6 +98,7 @@ The following table shows a configuration option's name, type, and the default v
 |[use-geoip2](#use-geoip2)|bool|"false"|
 |[enable-brotli](#enable-brotli)|bool|"false"|
 |[brotli-level](#brotli-level)|int|4|
+|[brotli-min-length](#brotli-min-length)|int|20|
 |[brotli-types](#brotli-types)|string|"application/xml+rss application/atom+xml application/javascript application/x-javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/svg+xml image/x-icon text/css text/javascript text/plain text/x-component"|
 |[use-http2](#use-http2)|bool|"true"|
 |[gzip-level](#gzip-level)|int|1|
@@ -311,7 +312,7 @@ _References:_
 
 ## disable-access-log
 
-Disables the Access Log from the entire Ingress Controller. _**default:**_ '"false"'
+Disables the Access Log from the entire Ingress Controller. _**default:**_ `false`
 
 _References:_
 [http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log](http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log)
@@ -495,7 +496,7 @@ _**default:**_ "0.0.0.0/0"
 
 ## proxy-set-headers
 
-Sets custom headers from named configmap before sending traffic to backends. The value format is namespace/name.  See [example](https://github.com/kubernetes/ingress-nginx/tree/main/docs/examples/customization/custom-headers)
+Sets custom headers from named configmap before sending traffic to backends. The value format is namespace/name.  See [example](https://kubernetes.github.io/ingress-nginx/examples/customization/custom-headers/)
 
 ## server-name-hash-max-size
 
@@ -665,6 +666,10 @@ The default mime type list to compress is: `application/xml+rss application/atom
 
 Sets the Brotli Compression Level that will be used. _**default:**_ 4
 
+## brotli-min-length
+
+Minimum length of responses, in bytes, that will be eligible for brotli compression. _**default:**_ 20
+
 ## brotli-types
 
 Sets the MIME Types that will be compressed on-the-fly by brotli.
diff --git a/docs/user-guide/third-party-addons/opentracing.md b/docs/user-guide/third-party-addons/opentracing.md
index e27f32f6b..27c64e300 100644
--- a/docs/user-guide/third-party-addons/opentracing.md
+++ b/docs/user-guide/third-party-addons/opentracing.md
@@ -185,6 +185,7 @@ In the Zipkin interface we can see the details:
       metadata:
         name: echo-ingress
       spec:
+        ingressClassName: nginx
         rules:
         - host: example.com
           http:
diff --git a/go.mod b/go.mod
index 225d191a3..49e380774 100644
--- a/go.mod
+++ b/go.mod
@@ -78,6 +78,7 @@ require (
 	github.com/go-openapi/jsonreference v0.19.3 // indirect
 	github.com/go-openapi/spec v0.19.5 // indirect
 	github.com/go-openapi/swag v0.19.5 // indirect
+	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
 	github.com/godbus/dbus/v5 v5.0.4 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
diff --git a/go.sum b/go.sum
index bdc542758..21a2bb5c4 100644
--- a/go.sum
+++ b/go.sum
@@ -269,6 +269,7 @@ github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2K
 github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
 github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
diff --git a/internal/ingress/annotations/cors/main.go b/internal/ingress/annotations/cors/main.go
index 44947a014..d2f232af1 100644
--- a/internal/ingress/annotations/cors/main.go
+++ b/internal/ingress/annotations/cors/main.go
@@ -18,8 +18,10 @@ package cors
 
 import (
 	"regexp"
+	"strings"
 
 	networking "k8s.io/api/networking/v1"
+	"k8s.io/klog/v2"
 
 	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
 	"k8s.io/ingress-nginx/internal/ingress/resolver"
@@ -36,7 +38,7 @@ var (
 	// Regex are defined here to prevent information leak, if user tries to set anything not valid
 	// that could cause the Response to contain some internal value/variable (like returning $pid, $upstream_addr, etc)
 	// Origin must contain a http/s Origin (including or not the port) or the value '*'
-	corsOriginRegex = regexp.MustCompile(`^(https?://[A-Za-z0-9\-\.]*(:[0-9]+)?|\*)?$`)
+	corsOriginRegex = regexp.MustCompile(`^(https?://(\*\.)?[A-Za-z0-9\-\.]*(:[0-9]+)?|\*)?$`)
 	// Method must contain valid methods list (PUT, GET, POST, BLA)
 	// May contain or not spaces between each verb
 	corsMethodsRegex = regexp.MustCompile(`^([A-Za-z]+,?\s?)+$`)
@@ -54,13 +56,13 @@ type cors struct {
 
 // Config contains the Cors configuration to be used in the Ingress
 type Config struct {
-	CorsEnabled          bool   `json:"corsEnabled"`
-	CorsAllowOrigin      string `json:"corsAllowOrigin"`
-	CorsAllowMethods     string `json:"corsAllowMethods"`
-	CorsAllowHeaders     string `json:"corsAllowHeaders"`
-	CorsAllowCredentials bool   `json:"corsAllowCredentials"`
-	CorsExposeHeaders    string `json:"corsExposeHeaders"`
-	CorsMaxAge           int    `json:"corsMaxAge"`
+	CorsEnabled          bool     `json:"corsEnabled"`
+	CorsAllowOrigin      []string `json:"corsAllowOrigin"`
+	CorsAllowMethods     string   `json:"corsAllowMethods"`
+	CorsAllowHeaders     string   `json:"corsAllowHeaders"`
+	CorsAllowCredentials bool     `json:"corsAllowCredentials"`
+	CorsExposeHeaders    string   `json:"corsExposeHeaders"`
+	CorsMaxAge           int      `json:"corsMaxAge"`
 }
 
 // NewParser creates a new CORS annotation parser
@@ -91,13 +93,20 @@ func (c1 *Config) Equal(c2 *Config) bool {
 	if c1.CorsAllowMethods != c2.CorsAllowMethods {
 		return false
 	}
-	if c1.CorsAllowOrigin != c2.CorsAllowOrigin {
+	if c1.CorsEnabled != c2.CorsEnabled {
 		return false
 	}
-	if c1.CorsEnabled != c2.CorsEnabled {
+
+	if len(c1.CorsAllowOrigin) != len(c2.CorsAllowOrigin) {
 		return false
 	}
 
+	for i, v := range c1.CorsAllowOrigin {
+		if v != c2.CorsAllowOrigin[i] {
+			return false
+		}
+	}
+
 	return true
 }
 
@@ -112,9 +121,23 @@ func (c cors) Parse(ing *networking.Ingress) (interface{}, error) {
 		config.CorsEnabled = false
 	}
 
-	config.CorsAllowOrigin, err = parser.GetStringAnnotation("cors-allow-origin", ing)
-	if err != nil || !corsOriginRegex.MatchString(config.CorsAllowOrigin) {
-		config.CorsAllowOrigin = "*"
+	unparsedOrigins, err := parser.GetStringAnnotation("cors-allow-origin", ing)
+	if err == nil {
+		config.CorsAllowOrigin = strings.Split(unparsedOrigins, ",")
+		for i, origin := range config.CorsAllowOrigin {
+			origin = strings.TrimSpace(origin)
+			if origin == "*" {
+				config.CorsAllowOrigin = []string{"*"}
+				break
+			}
+			if !corsOriginRegex.MatchString(origin) {
+				klog.Errorf("Error parsing cors-allow-origin parameters. Supplied incorrect origin: %s. Skipping.", origin)
+				config.CorsAllowOrigin = append(config.CorsAllowOrigin[:i], config.CorsAllowOrigin[i+1:]...)
+			}
+			klog.Infof("Current config.corsAllowOrigin %v", config.CorsAllowOrigin)
+		}
+	} else {
+		config.CorsAllowOrigin = []string{"*"}
 	}
 
 	config.CorsAllowHeaders, err = parser.GetStringAnnotation("cors-allow-headers", ing)
@@ -143,5 +166,4 @@ func (c cors) Parse(ing *networking.Ingress) (interface{}, error) {
 	}
 
 	return config, nil
-
 }
diff --git a/internal/ingress/controller/config/config.go b/internal/ingress/controller/config/config.go
index b1a5fc8c4..2d853cfb0 100644
--- a/internal/ingress/controller/config/config.go
+++ b/internal/ingress/controller/config/config.go
@@ -413,6 +413,9 @@ type Configuration struct {
 	// Brotli Compression Level that will be used
 	BrotliLevel int `json:"brotli-level,omitempty"`
 
+	// Minimum length of responses, in bytes, that will be eligible for brotli compression
+	BrotliMinLength int `json:"brotli-min-length,omitempty"`
+
 	// MIME Types that will be compressed on-the-fly using Brotli module
 	BrotliTypes string `json:"brotli-types,omitempty"`
 
@@ -778,6 +781,7 @@ func NewDefault() Configuration {
 		BlockUserAgents:                  defBlockEntity,
 		BlockReferers:                    defBlockEntity,
 		BrotliLevel:                      4,
+		BrotliMinLength:                  20,
 		BrotliTypes:                      brotliTypes,
 		ClientHeaderBufferSize:           "1k",
 		ClientHeaderTimeout:              60,
diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go
index f935f5fff..d648bb3ae 100644
--- a/internal/ingress/controller/controller.go
+++ b/internal/ingress/controller/controller.go
@@ -215,6 +215,8 @@ func (n *NGINXController) syncIngress(interface{}) error {
 // CheckIngress returns an error in case the provided ingress, when added
 // to the current configuration, generates an invalid configuration
 func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
+	startCheck := time.Now().UnixNano() / 1000000
+
 	if ing == nil {
 		// no ingress to add, no state change
 		return nil
@@ -233,7 +235,7 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
 	if n.cfg.DisableCatchAll && ing.Spec.DefaultBackend != nil {
 		return fmt.Errorf("This deployment is trying to create a catch-all ingress while DisableCatchAll flag is set to true. Remove '.spec.backend' or set DisableCatchAll flag to false.")
 	}
-
+	startRender := time.Now().UnixNano() / 1000000
 	cfg := n.store.GetBackendConfiguration()
 	cfg.Resolver = n.resolver
 
@@ -267,7 +269,7 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
 		Ingress:           *ing,
 		ParsedAnnotations: annotations.NewAnnotationExtractor(n.store).Extract(ing),
 	})
-
+	startTest := time.Now().UnixNano() / 1000000
 	_, servers, pcfg := n.getConfiguration(ings)
 
 	err := checkOverlap(ing, allIngresses, servers)
@@ -275,9 +277,10 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
 		n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name)
 		return err
 	}
-
+	testedSize := len(ings)
 	if n.cfg.DisableFullValidationTest {
 		_, _, pcfg = n.getConfiguration(ings[len(ings)-1:])
+		testedSize = 1
 	}
 
 	content, err := n.generateTemplate(cfg, *pcfg)
@@ -291,8 +294,16 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
 		n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name)
 		return err
 	}
-
 	n.metricCollector.IncCheckCount(ing.ObjectMeta.Namespace, ing.Name)
+	endCheck := time.Now().UnixNano() / 1000000
+	n.metricCollector.SetAdmissionMetrics(
+		float64(testedSize),
+		float64(endCheck-startTest)/1000,
+		float64(len(ings)),
+		float64(startTest-startRender)/1000,
+		float64(len(content)),
+		float64(endCheck-startCheck)/1000,
+	)
 	return nil
 }
 
diff --git a/internal/ingress/controller/nginx.go b/internal/ingress/controller/nginx.go
index ddde11bc8..b77d1d870 100644
--- a/internal/ingress/controller/nginx.go
+++ b/internal/ingress/controller/nginx.go
@@ -241,7 +241,8 @@ type NGINXController struct {
 
 	store store.Storer
 
-	metricCollector metric.Collector
+	metricCollector    metric.Collector
+	admissionCollector metric.Collector
 
 	validationWebhookServer *http.Server
 
diff --git a/internal/ingress/controller/template/template.go b/internal/ingress/controller/template/template.go
index dc4b78c1a..1147c8e5d 100644
--- a/internal/ingress/controller/template/template.go
+++ b/internal/ingress/controller/template/template.go
@@ -276,6 +276,7 @@ var (
 		"shouldLoadAuthDigestModule":         shouldLoadAuthDigestModule,
 		"shouldLoadInfluxDBModule":           shouldLoadInfluxDBModule,
 		"buildServerName":                    buildServerName,
+		"buildCorsOriginRegex":               buildCorsOriginRegex,
 	}
 )
 
@@ -1676,3 +1677,29 @@ func convertGoSliceIntoLuaTable(goSliceInterface interface{}, emptyStringAsNil b
 		return "", fmt.Errorf("could not process type: %s", kind)
 	}
 }
+
+func buildOriginRegex(origin string) string {
+	origin = regexp.QuoteMeta(origin)
+	origin = strings.Replace(origin, "\\*", "[A-Za-z0-9]+", 1)
+	return fmt.Sprintf("(%s)", origin)
+}
+
+func buildCorsOriginRegex(corsOrigins []string) string {
+	if len(corsOrigins) == 1 && corsOrigins[0] == "*" {
+		return "set $http_origin *;\nset $cors 'true';"
+	}
+
+	var originsRegex string = "if ($http_origin ~* ("
+	for i, origin := range corsOrigins {
+		originTrimmed := strings.TrimSpace(origin)
+		if len(originTrimmed) > 0 {
+			builtOrigin := buildOriginRegex(originTrimmed)
+			originsRegex += builtOrigin
+			if i != len(corsOrigins)-1 {
+				originsRegex = originsRegex + "|"
+			}
+		}
+	}
+	originsRegex = originsRegex + ")$ ) { set $cors 'true'; }"
+	return originsRegex
+}
diff --git a/internal/ingress/metric/collectors/admission.go b/internal/ingress/metric/collectors/admission.go
new file mode 100644
index 000000000..cf42fbaa1
--- /dev/null
+++ b/internal/ingress/metric/collectors/admission.go
@@ -0,0 +1,157 @@
+/*
+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 collectors
+
+import (
+	"fmt"
+
+	"github.com/prometheus/client_golang/prometheus"
+	"k8s.io/klog/v2"
+)
+
+// AdmissionCollector stores prometheus metrics of the admission webhook
+type AdmissionCollector struct {
+	prometheus.Collector
+
+	testedIngressLength prometheus.Gauge
+	testedIngressTime   prometheus.Gauge
+
+	renderingIngressLength prometheus.Gauge
+	renderingIngressTime   prometheus.Gauge
+
+	admissionTime prometheus.Gauge
+
+	testedConfigurationSize prometheus.Gauge
+
+	constLabels prometheus.Labels
+	labels      prometheus.Labels
+}
+
+// NewAdmissionCollector creates a new AdmissionCollector instance for the admission collector
+func NewAdmissionCollector(pod, namespace, class string) *AdmissionCollector {
+	constLabels := prometheus.Labels{
+		"controller_namespace": namespace,
+		"controller_class":     class,
+		"controller_pod":       pod,
+	}
+
+	am := &AdmissionCollector{
+		constLabels: constLabels,
+
+		labels: prometheus.Labels{
+			"namespace": namespace,
+			"class":     class,
+		},
+
+		testedIngressLength: prometheus.NewGauge(
+			prometheus.GaugeOpts{
+				Name:        "admission_tested_ingresses",
+				Help:        "The length of ingresses processed by the admission controller",
+				Namespace:   PrometheusNamespace,
+				ConstLabels: constLabels,
+			}),
+		testedIngressTime: prometheus.NewGauge(
+			prometheus.GaugeOpts{
+				Name:        "admission_tested_duration",
+				Help:        "The processing duration of the admission controller tests (float seconds)",
+				Namespace:   PrometheusNamespace,
+				ConstLabels: constLabels,
+			}),
+		renderingIngressLength: prometheus.NewGauge(
+			prometheus.GaugeOpts{
+				Name:        "admission_render_ingresses",
+				Help:        "The length of ingresses rendered by the admission controller",
+				Namespace:   PrometheusNamespace,
+				ConstLabels: constLabels,
+			}),
+		renderingIngressTime: prometheus.NewGauge(
+			prometheus.GaugeOpts{
+				Name:        "admission_render_duration",
+				Help:        "The processing duration of ingresses rendering by the admission controller (float seconds)",
+				Namespace:   PrometheusNamespace,
+				ConstLabels: constLabels,
+			}),
+		testedConfigurationSize: prometheus.NewGauge(
+			prometheus.GaugeOpts{
+				Namespace:   PrometheusNamespace,
+				Name:        "admission_config_size",
+				Help:        "The size of the tested configuration",
+				ConstLabels: constLabels,
+			}),
+		admissionTime: prometheus.NewGauge(
+			prometheus.GaugeOpts{
+				Name:        "admission_roundtrip_duration",
+				Help:        "The complete duration of the admission controller at the time to process a new event (float seconds)",
+				Namespace:   PrometheusNamespace,
+				ConstLabels: constLabels,
+			}),
+	}
+	return am
+}
+
+// Describe implements prometheus.Collector
+func (am AdmissionCollector) Describe(ch chan<- *prometheus.Desc) {
+	am.testedIngressLength.Describe(ch)
+	am.testedIngressTime.Describe(ch)
+	am.renderingIngressLength.Describe(ch)
+	am.renderingIngressTime.Describe(ch)
+	am.testedConfigurationSize.Describe(ch)
+	am.admissionTime.Describe(ch)
+}
+
+// Collect implements the prometheus.Collector interface.
+func (am AdmissionCollector) Collect(ch chan<- prometheus.Metric) {
+	am.testedIngressLength.Collect(ch)
+	am.testedIngressTime.Collect(ch)
+	am.renderingIngressLength.Collect(ch)
+	am.renderingIngressTime.Collect(ch)
+	am.testedConfigurationSize.Collect(ch)
+	am.admissionTime.Collect(ch)
+}
+
+// ByteFormat formats humanReadable bytes
+func ByteFormat(bytes int64) string {
+	const unit = 1000
+	if bytes < unit {
+		return fmt.Sprintf("%d B", bytes)
+	}
+	div, exp := int64(unit), 0
+	for n := bytes / unit; n >= unit; n /= unit {
+		div *= unit
+		exp++
+	}
+	return fmt.Sprintf("%.1f%cB",
+		float64(bytes)/float64(div), "kMGTPE"[exp])
+}
+
+// SetAdmissionMetrics sets the values for AdmissionMetrics that can be called externally
+func (am *AdmissionCollector) SetAdmissionMetrics(testedIngressLength float64, testedIngressTime float64, renderingIngressLength float64, renderingIngressTime float64, testedConfigurationSize float64, admissionTime float64) {
+	am.testedIngressLength.Set(testedIngressLength)
+	am.testedIngressTime.Set(testedIngressTime)
+	am.renderingIngressLength.Set(renderingIngressLength)
+	am.renderingIngressTime.Set(renderingIngressTime)
+	am.testedConfigurationSize.Set(testedConfigurationSize)
+	am.admissionTime.Set(admissionTime)
+	klog.Infof("processed ingress via admission controller {testedIngressLength:%v testedIngressTime:%vs renderingIngressLength:%v renderingIngressTime:%vs admissionTime:%vs testedConfigurationSize:%v}",
+		testedIngressLength,
+		testedIngressTime,
+		renderingIngressLength,
+		renderingIngressTime,
+		ByteFormat(int64(testedConfigurationSize)),
+		admissionTime,
+	)
+}
diff --git a/internal/ingress/metric/dummy.go b/internal/ingress/metric/dummy.go
index 59a9144e0..922a21604 100644
--- a/internal/ingress/metric/dummy.go
+++ b/internal/ingress/metric/dummy.go
@@ -32,6 +32,9 @@ type DummyCollector struct{}
 // ConfigSuccess ...
 func (dc DummyCollector) ConfigSuccess(uint64, bool) {}
 
+// SetAdmissionMetrics ...
+func (dc DummyCollector) SetAdmissionMetrics(float64, float64, float64, float64, float64, float64) {}
+
 // IncReloadCount ...
 func (dc DummyCollector) IncReloadCount() {}
 
@@ -48,10 +51,10 @@ func (dc DummyCollector) IncCheckErrorCount(string, string) {}
 func (dc DummyCollector) RemoveMetrics(ingresses, endpoints []string) {}
 
 // Start ...
-func (dc DummyCollector) Start() {}
+func (dc DummyCollector) Start(admissionStatus string) {}
 
 // Stop ...
-func (dc DummyCollector) Stop() {}
+func (dc DummyCollector) Stop(admissionStatus string) {}
 
 // SetSSLExpireTime ...
 func (dc DummyCollector) SetSSLExpireTime([]*ingress.Server) {}
diff --git a/internal/ingress/metric/main.go b/internal/ingress/metric/main.go
index 64810dd36..0cc07fe28 100644
--- a/internal/ingress/metric/main.go
+++ b/internal/ingress/metric/main.go
@@ -36,6 +36,8 @@ type Collector interface {
 	IncReloadCount()
 	IncReloadErrorCount()
 
+	SetAdmissionMetrics(float64, float64, float64, float64, float64, float64)
+
 	OnStartedLeading(string)
 	OnStoppedLeading(string)
 
@@ -49,15 +51,16 @@ type Collector interface {
 	// SetHosts sets the hostnames that are being served by the ingress controller
 	SetHosts(sets.String)
 
-	Start()
-	Stop()
+	Start(string)
+	Stop(string)
 }
 
 type collector struct {
 	nginxStatus  collectors.NGINXStatusCollector
 	nginxProcess collectors.NGINXProcessCollector
 
-	ingressController *collectors.Controller
+	ingressController   *collectors.Controller
+	admissionController *collectors.AdmissionCollector
 
 	socket *collectors.SocketCollector
 
@@ -90,11 +93,14 @@ func NewCollector(metricsPerHost bool, registry *prometheus.Registry, ingresscla
 
 	ic := collectors.NewController(podName, podNamespace, ingressclass)
 
+	am := collectors.NewAdmissionCollector(podName, podNamespace, ingressclass)
+
 	return Collector(&collector{
 		nginxStatus:  nc,
 		nginxProcess: pc,
 
-		ingressController: ic,
+		admissionController: am,
+		ingressController:   ic,
 
 		socket: s,
 
@@ -127,9 +133,12 @@ func (c *collector) RemoveMetrics(ingresses, hosts []string) {
 	c.ingressController.RemoveMetrics(hosts, c.registry)
 }
 
-func (c *collector) Start() {
+func (c *collector) Start(admissionStatus string) {
 	c.registry.MustRegister(c.nginxStatus)
 	c.registry.MustRegister(c.nginxProcess)
+	if admissionStatus != "" {
+		c.registry.MustRegister(c.admissionController)
+	}
 	c.registry.MustRegister(c.ingressController)
 	c.registry.MustRegister(c.socket)
 
@@ -143,9 +152,12 @@ func (c *collector) Start() {
 	go c.socket.Start()
 }
 
-func (c *collector) Stop() {
+func (c *collector) Stop(admissionStatus string) {
 	c.registry.Unregister(c.nginxStatus)
 	c.registry.Unregister(c.nginxProcess)
+	if admissionStatus != "" {
+		c.registry.Unregister(c.admissionController)
+	}
 	c.registry.Unregister(c.ingressController)
 	c.registry.Unregister(c.socket)
 
@@ -167,6 +179,17 @@ func (c *collector) SetHosts(hosts sets.String) {
 	c.socket.SetHosts(hosts)
 }
 
+func (c *collector) SetAdmissionMetrics(testedIngressLength float64, testedIngressTime float64, renderingIngressLength float64, renderingIngressTime float64, testedConfigurationSize float64, admissionTime float64) {
+	c.admissionController.SetAdmissionMetrics(
+		testedIngressLength,
+		testedIngressTime,
+		renderingIngressLength,
+		renderingIngressTime,
+		testedConfigurationSize,
+		admissionTime,
+	)
+}
+
 // OnStartedLeading indicates the pod was elected as the leader
 func (c *collector) OnStartedLeading(electionID string) {
 	setLeader(true)
diff --git a/rootfs/etc/nginx/template/nginx.tmpl b/rootfs/etc/nginx/template/nginx.tmpl
index bfe0703ff..d579165b7 100755
--- a/rootfs/etc/nginx/template/nginx.tmpl
+++ b/rootfs/etc/nginx/template/nginx.tmpl
@@ -320,6 +320,7 @@ http {
     {{ if $cfg.EnableBrotli }}
     brotli on;
     brotli_comp_level {{ $cfg.BrotliLevel }};
+    brotli_min_length {{ $cfg.BrotliMinLength }};
     brotli_types {{ $cfg.BrotliTypes }};
     {{ end }}
 
@@ -866,22 +867,33 @@ stream {
 {{ define "CORS" }}
      {{ $cors := .CorsConfig }}
      # Cors Preflight methods needs additional options and different Return Code
+     {{ if $cors.CorsAllowOrigin }}
+        {{ buildCorsOriginRegex $cors.CorsAllowOrigin }}
+     {{ end }}
      if ($request_method = 'OPTIONS') {
-        more_set_headers 'Access-Control-Allow-Origin: {{ $cors.CorsAllowOrigin }}';
+        set $cors ${cors}options;
+     }
+
+     if ($cors = "true") {
+        more_set_headers 'Access-Control-Allow-Origin: $http_origin';
         {{ if $cors.CorsAllowCredentials }} more_set_headers 'Access-Control-Allow-Credentials: {{ $cors.CorsAllowCredentials }}'; {{ end }}
         more_set_headers 'Access-Control-Allow-Methods: {{ $cors.CorsAllowMethods }}';
         more_set_headers 'Access-Control-Allow-Headers: {{ $cors.CorsAllowHeaders }}';
         {{ if not (empty $cors.CorsExposeHeaders) }} more_set_headers 'Access-Control-Expose-Headers: {{ $cors.CorsExposeHeaders }}'; {{ end }}
         more_set_headers 'Access-Control-Max-Age: {{ $cors.CorsMaxAge }}';
-        more_set_headers 'Content-Type: text/plain charset=UTF-8';
-        more_set_headers 'Content-Length: 0';
-        return 204;
      }
 
-        more_set_headers 'Access-Control-Allow-Origin: {{ $cors.CorsAllowOrigin }}';
+     if ($cors = "trueoptions") {
+        more_set_headers 'Access-Control-Allow-Origin: $http_origin';
         {{ if $cors.CorsAllowCredentials }} more_set_headers 'Access-Control-Allow-Credentials: {{ $cors.CorsAllowCredentials }}'; {{ end }}
+        more_set_headers 'Access-Control-Allow-Methods: {{ $cors.CorsAllowMethods }}';
+        more_set_headers 'Access-Control-Allow-Headers: {{ $cors.CorsAllowHeaders }}';
         {{ if not (empty $cors.CorsExposeHeaders) }} more_set_headers 'Access-Control-Expose-Headers: {{ $cors.CorsExposeHeaders }}'; {{ end }}
-
+        more_set_headers 'Access-Control-Max-Age: {{ $cors.CorsMaxAge }}';
+        more_set_headers 'Content-Type: text/plain charset=UTF-8';
+        more_set_headers 'Content-Length: 0';
+        return 204;
+     }
 {{ end }}
 
 {{/* definition of server-template to avoid repetitions with server-alias */}}


Test patch:
diff --git a/internal/ingress/annotations/annotations_test.go b/internal/ingress/annotations/annotations_test.go
index ada81117d..8eec999cc 100644
--- a/internal/ingress/annotations/annotations_test.go
+++ b/internal/ingress/annotations/annotations_test.go
@@ -215,15 +215,15 @@ func TestCors(t *testing.T) {
 		corsenabled bool
 		methods     string
 		headers     string
-		origin      string
+		origin      []string
 		credentials bool
 		expose      string
 	}{
-		{map[string]string{annotationCorsEnabled: "true"}, true, defaultCorsMethods, defaultCorsHeaders, "*", true, ""},
-		{map[string]string{annotationCorsEnabled: "true", annotationCorsAllowMethods: "POST, GET, OPTIONS", annotationCorsAllowHeaders: "$nginx_version", annotationCorsAllowCredentials: "false", annotationCorsExposeHeaders: "X-CustomResponseHeader"}, true, "POST, GET, OPTIONS", defaultCorsHeaders, "*", false, "X-CustomResponseHeader"},
-		{map[string]string{annotationCorsEnabled: "true", annotationCorsAllowCredentials: "false"}, true, defaultCorsMethods, defaultCorsHeaders, "*", false, ""},
-		{map[string]string{}, false, defaultCorsMethods, defaultCorsHeaders, "*", true, ""},
-		{nil, false, defaultCorsMethods, defaultCorsHeaders, "*", true, ""},
+		{map[string]string{annotationCorsEnabled: "true"}, true, defaultCorsMethods, defaultCorsHeaders, []string{"*"}, true, ""},
+		{map[string]string{annotationCorsEnabled: "true", annotationCorsAllowMethods: "POST, GET, OPTIONS", annotationCorsAllowHeaders: "$nginx_version", annotationCorsAllowCredentials: "false", annotationCorsExposeHeaders: "X-CustomResponseHeader"}, true, "POST, GET, OPTIONS", defaultCorsHeaders, []string{"*"}, false, "X-CustomResponseHeader"},
+		{map[string]string{annotationCorsEnabled: "true", annotationCorsAllowCredentials: "false"}, true, defaultCorsMethods, defaultCorsHeaders, []string{"*"}, false, ""},
+		{map[string]string{}, false, defaultCorsMethods, defaultCorsHeaders, []string{"*"}, true, ""},
+		{nil, false, defaultCorsMethods, defaultCorsHeaders, []string{"*"}, true, ""},
 	}
 
 	for _, foo := range fooAnns {
@@ -243,12 +243,18 @@ func TestCors(t *testing.T) {
 			t.Errorf("Returned %v but expected %v for Cors Methods", r.CorsAllowMethods, foo.methods)
 		}
 
-		if r.CorsAllowOrigin != foo.origin {
-			t.Errorf("Returned %v but expected %v for Cors Methods", r.CorsAllowOrigin, foo.origin)
+		if len(r.CorsAllowOrigin) != len(foo.origin) {
+			t.Errorf("Lengths of Cors Origins are not equal. Expected %v - Actual %v", r.CorsAllowOrigin, foo.origin)
+		}
+
+		for i, v := range r.CorsAllowOrigin {
+			if v != foo.origin[i] {
+				t.Errorf("Values of Cors Origins are not equal. Expected %v - Actual %v", r.CorsAllowOrigin, foo.origin)
+			}
 		}
 
 		if r.CorsAllowCredentials != foo.credentials {
-			t.Errorf("Returned %v but expected %v for Cors Methods", r.CorsAllowCredentials, foo.credentials)
+			t.Errorf("Returned %v but expected %v for Cors Credentials", r.CorsAllowCredentials, foo.credentials)
 		}
 
 	}
diff --git a/internal/ingress/annotations/cors/main_test.go b/internal/ingress/annotations/cors/main_test.go
index a65bf12cd..086a59d89 100644
--- a/internal/ingress/annotations/cors/main_test.go
+++ b/internal/ingress/annotations/cors/main_test.go
@@ -110,7 +110,7 @@ func TestIngressCorsConfigValid(t *testing.T) {
 		t.Errorf("expected %v but returned %v", data[parser.GetAnnotationWithPrefix("cors-allow-methods")], nginxCors.CorsAllowMethods)
 	}
 
-	if nginxCors.CorsAllowOrigin != "https://origin123.test.com:4443" {
+	if nginxCors.CorsAllowOrigin[0] != "https://origin123.test.com:4443" {
 		t.Errorf("expected %v but returned %v", data[parser.GetAnnotationWithPrefix("cors-allow-origin")], nginxCors.CorsAllowOrigin)
 	}
 
@@ -164,10 +164,6 @@ func TestIngressCorsConfigInvalid(t *testing.T) {
 		t.Errorf("expected %v but returned %v", defaultCorsHeaders, nginxCors.CorsAllowMethods)
 	}
 
-	if nginxCors.CorsAllowOrigin != "*" {
-		t.Errorf("expected %v but returned %v", "*", nginxCors.CorsAllowOrigin)
-	}
-
 	if nginxCors.CorsExposeHeaders != "" {
 		t.Errorf("expected %v but returned %v", "", nginxCors.CorsExposeHeaders)
 	}
diff --git a/internal/ingress/metric/collectors/admission_test.go b/internal/ingress/metric/collectors/admission_test.go
new file mode 100644
index 000000000..68208ad3e
--- /dev/null
+++ b/internal/ingress/metric/collectors/admission_test.go
@@ -0,0 +1,122 @@
+/*
+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 collectors
+
+import (
+	"testing"
+
+	"github.com/prometheus/client_golang/prometheus"
+)
+
+func TestAdmissionCounters(t *testing.T) {
+	const (
+		metadataFirst = `
+			# HELP nginx_ingress_controller_admission_config_size The size of the tested configuration
+			# TYPE nginx_ingress_controller_admission_config_size gauge
+			# HELP nginx_ingress_controller_admission_roundtrip_duration The complete duration of the admission controller at the time to process a new event (float seconds)
+			# TYPE nginx_ingress_controller_admission_roundtrip_duration gauge
+		`
+		metadataSecond = `
+			# HELP nginx_ingress_controller_admission_render_ingresses The length of ingresses rendered by the admission controller
+			# TYPE nginx_ingress_controller_admission_render_ingresses gauge
+			# HELP nginx_ingress_controller_admission_tested_duration The processing duration of the admission controller tests (float seconds)
+			# TYPE nginx_ingress_controller_admission_tested_duration gauge
+		`
+		metadataThird = `
+			# HELP nginx_ingress_controller_admission_config_size The size of the tested configuration
+			# TYPE nginx_ingress_controller_admission_config_size gauge
+			# HELP nginx_ingress_controller_admission_render_duration The processing duration of ingresses rendering by the admission controller (float seconds)
+			# TYPE nginx_ingress_controller_admission_render_duration gauge
+			# HELP nginx_ingress_controller_admission_render_ingresses The length of ingresses rendered by the admission controller
+			# TYPE nginx_ingress_controller_admission_render_ingresses gauge
+			# HELP nginx_ingress_controller_admission_roundtrip_duration The complete duration of the admission controller at the time to process a new event (float seconds)
+			# TYPE nginx_ingress_controller_admission_roundtrip_duration gauge
+			# HELP nginx_ingress_controller_admission_tested_ingresses The length of ingresses processed by the admission controller
+			# TYPE nginx_ingress_controller_admission_tested_ingresses gauge
+			# HELP nginx_ingress_controller_admission_tested_duration The processing duration of the admission controller tests (float seconds)
+			# TYPE nginx_ingress_controller_admission_tested_duration gauge
+		`
+	)
+	cases := []struct {
+		name    string
+		test    func(*AdmissionCollector)
+		metrics []string
+		want    string
+	}{
+		{
+			name: "should return 0 as values on a fresh initiated collector",
+			test: func(am *AdmissionCollector) {
+			},
+			want: metadataFirst + `
+				nginx_ingress_controller_admission_config_size{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 0
+				nginx_ingress_controller_admission_roundtrip_duration{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 0
+			`,
+			metrics: []string{"nginx_ingress_controller_admission_config_size", "nginx_ingress_controller_admission_roundtrip_duration"},
+		},
+		{
+			name: "set admission metrics to 1 in all fields and validate next set",
+			test: func(am *AdmissionCollector) {
+				am.SetAdmissionMetrics(1, 1, 1, 1, 1, 1)
+			},
+			want: metadataSecond + `
+				nginx_ingress_controller_admission_render_ingresses{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 1
+				nginx_ingress_controller_admission_tested_duration{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 1
+			`,
+			metrics: []string{"nginx_ingress_controller_admission_render_ingresses", "nginx_ingress_controller_admission_tested_duration"},
+		},
+		{
+			name: "set admission metrics to 5 in all fields and validate all sets",
+			test: func(am *AdmissionCollector) {
+				am.SetAdmissionMetrics(5, 5, 5, 5, 5, 5)
+			},
+			want: metadataThird + `
+				nginx_ingress_controller_admission_config_size{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 5
+				nginx_ingress_controller_admission_render_duration{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 5
+				nginx_ingress_controller_admission_render_ingresses{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 5
+				nginx_ingress_controller_admission_roundtrip_duration{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 5
+				nginx_ingress_controller_admission_tested_ingresses{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 5
+				nginx_ingress_controller_admission_tested_duration{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 5
+			`,
+			metrics: []string{
+				"nginx_ingress_controller_admission_config_size",
+				"nginx_ingress_controller_admission_render_duration",
+				"nginx_ingress_controller_admission_render_ingresses",
+				"nginx_ingress_controller_admission_roundtrip_duration",
+				"nginx_ingress_controller_admission_tested_ingresses",
+				"nginx_ingress_controller_admission_tested_duration",
+			},
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			am := NewAdmissionCollector("pod", "default", "nginx")
+			reg := prometheus.NewPedanticRegistry()
+			if err := reg.Register(am); err != nil {
+				t.Errorf("registering collector failed: %s", err)
+			}
+
+			c.test(am)
+
+			if err := GatherAndCompare(am, c.want, c.metrics, reg); err != nil {
+				t.Errorf("unexpected collecting result:\n%s", err)
+			}
+
+			reg.Unregister(am)
+		})
+	}
+}
diff --git a/test/e2e/annotations/cors.go b/test/e2e/annotations/cors.go
index e4cdd32b4..c17eb0b20 100644
--- a/test/e2e/annotations/cors.go
+++ b/test/e2e/annotations/cors.go
@@ -44,10 +44,12 @@ var _ = framework.DescribeAnnotation("cors-*", func() {
 		f.WaitForNginxServer(host,
 			func(server string) bool {
 				return strings.Contains(server, "more_set_headers 'Access-Control-Allow-Methods: GET, PUT, POST, DELETE, PATCH, OPTIONS';") &&
-					strings.Contains(server, "more_set_headers 'Access-Control-Allow-Origin: *';") &&
+					strings.Contains(server, "more_set_headers 'Access-Control-Allow-Origin: $http_origin';") &&
 					strings.Contains(server, "more_set_headers 'Access-Control-Allow-Headers: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';") &&
 					strings.Contains(server, "more_set_headers 'Access-Control-Max-Age: 1728000';") &&
-					strings.Contains(server, "more_set_headers 'Access-Control-Allow-Credentials: true';")
+					strings.Contains(server, "more_set_headers 'Access-Control-Allow-Credentials: true';") &&
+					strings.Contains(server, "set $http_origin *;") &&
+					strings.Contains(server, "$cors 'true';")
 			})
 
 		f.HTTPTestClient().
@@ -107,6 +109,7 @@ var _ = framework.DescribeAnnotation("cors-*", func() {
 
 	ginkgo.It("should allow origin for cors", func() {
 		host := "cors.foo.com"
+		origin := "https://origin.cors.com:8080"
 		annotations := map[string]string{
 			"nginx.ingress.kubernetes.io/enable-cors":       "true",
 			"nginx.ingress.kubernetes.io/cors-allow-origin": "https://origin.cors.com:8080",
@@ -115,10 +118,20 @@ var _ = framework.DescribeAnnotation("cors-*", func() {
 		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, "more_set_headers 'Access-Control-Allow-Origin: https://origin.cors.com:8080';")
-			})
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{origin})
 	})
 
 	ginkgo.It("should allow headers for cors", func() {
@@ -152,4 +165,450 @@ var _ = framework.DescribeAnnotation("cors-*", func() {
 				return strings.Contains(server, "more_set_headers 'Access-Control-Expose-Headers: X-CustomResponseHeader, X-CustomSecondHeader';")
 			})
 	})
+
+	ginkgo.It("should allow - single origin for multiple cors values", func() {
+		host := "cors.foo.com"
+		origin := "https://origin.cors.com:8080"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "https://origin.cors.com:8080, https://origin2.cors.com",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{origin})
+	})
+
+	ginkgo.It("should not allow - single origin for multiple cors values", func() {
+		host := "cors.foo.com"
+		origin := "http://no.origin.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "http://origin2.cors.com, https://origin.com",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		// the client should still receive a response but browsers should block the request
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().NotContainsKey("Access-Control-Allow-Origin")
+	})
+
+	ginkgo.It("should allow correct origins - single origin for multiple cors values", func() {
+		host := "cors.foo.com"
+		badOrigin := "origin.cors.com:8080"
+		origin1 := "https://origin2.cors.com"
+		origin2 := "https://origin.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "origin.cors.com:8080, https://origin2.cors.com, https://origin.com",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", badOrigin).
+			Expect().
+			Headers().NotContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin1).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin1).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{origin1})
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin2).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin2).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{origin2})
+	})
+
+	ginkgo.It("should not break functionality", func() {
+		host := "cors.foo.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "*",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{"*"})
+	})
+
+	ginkgo.It("should not break functionality - without `*`", func() {
+		host := "cors.foo.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors": "true",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{"*"})
+	})
+
+	ginkgo.It("should not break functionality with extra domain", func() {
+		host := "cors.foo.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "*, foo.bar.com",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{"*"})
+	})
+
+	ginkgo.It("should not match", func() {
+		host := "cors.foo.com"
+		origin := "https://fooxbar.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "https://foo.bar.com",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		// the client should still receive a response but browsers should block the request
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().NotContainsKey("Access-Control-Allow-Origin")
+	})
+
+	ginkgo.It("should allow - single origin with required port", func() {
+		host := "cors.foo.com"
+		origin := "http://origin.com:8080"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "http://origin.cors.com:8080, http://origin.com:8080",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		// the client should still receive a response but browsers should block the request
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{origin})
+	})
+
+	ginkgo.It("should not allow - single origin with port and origin without port", func() {
+		host := "cors.foo.com"
+		origin := "http://origin.com:8080"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "https://origin2.cors.com, http://origin.com",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().NotContainsKey("Access-Control-Allow-Origin")
+	})
+
+	ginkgo.It("should not allow - single origin without port and origin with required port", func() {
+		host := "cors.foo.com"
+		origin := "http://origin.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "http://origin.cors.com:8080, http://origin.com:8080",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		// the client should still receive a response but browsers should block the request
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().NotContainsKey("Access-Control-Allow-Origin")
+	})
+
+	ginkgo.It("should allow - matching origin with wildcard origin (2 subdomains)", func() {
+		host := "cors.foo.com"
+		origin := "http://foo.origin.cors.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "http://*.origin.cors.com, http://*.origin.com:8080",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{origin})
+	})
+
+	ginkgo.It("should not allow - unmatching origin with wildcard origin (2 subdomains)", func() {
+		host := "cors.foo.com"
+		origin := "http://bar.foo.origin.cors.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "http://*.origin.cors.com, http://*.origin.com:8080",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		// the client should still receive a response but browsers should block the request
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().NotContainsKey("Access-Control-Allow-Origin")
+	})
+
+	ginkgo.It("should allow - matching origin+port with wildcard origin", func() {
+		host := "cors.foo.com"
+		origin := "http://abc.origin.com:8080"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "http://origin.cors.com:8080, http://*.origin.com:8080",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{origin})
+	})
+
+	ginkgo.It("should not allow - portless origin with wildcard origin", func() {
+		host := "cors.foo.com"
+		origin := "http://abc.origin.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "http://origin.cors.com:8080, http://*.origin.com:8080",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		// the client should still receive a response but browsers should block the request
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().NotContainsKey("Access-Control-Allow-Origin")
+	})
+
+	ginkgo.It("should allow correct origins - missing subdomain + origin with wildcard origin and correct origin", func() {
+		host := "cors.foo.com"
+		badOrigin := "http://origin.com:8080"
+		origin := "http://bar.origin.com:8080"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "http://origin.cors.com:8080, http://*.origin.com:8080",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		// the client should still receive a response but browsers should block the request
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", badOrigin).
+			Expect().
+			Headers().NotContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{origin})
+	})
+
+	ginkgo.It("should allow - missing origins (should allow all origins)", func() {
+		host := "cors.foo.com"
+		origin := "http://origin.com"
+		origin2 := "http://book.origin.com"
+		origin3 := "test.origin.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "      ",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		// the client should still receive a response but browsers should block the request
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{"*"})
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin2).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin2).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{"*"})
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin3).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin3).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{"*"})
+	})
 })
diff --git a/test/e2e/settings/brotli.go b/test/e2e/settings/brotli.go
new file mode 100644
index 000000000..52092ee83
--- /dev/null
+++ b/test/e2e/settings/brotli.go
@@ -0,0 +1,74 @@
+/*
+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 settings
+
+import (
+	"fmt"
+	"net/http"
+	"strconv"
+	"strings"
+
+	"github.com/onsi/ginkgo"
+
+	"k8s.io/ingress-nginx/test/e2e/framework"
+)
+
+var _ = framework.IngressNginxDescribe("brotli", func() {
+	f := framework.NewDefaultFramework("brotli")
+
+	host := "brotli"
+
+	ginkgo.BeforeEach(func() {
+		f.NewHttpbinDeployment()
+	})
+
+	ginkgo.It("should only compress responses that meet the `brotli-min-length` condition", func() {
+		brotliMinLength := 24
+		contentEncoding := "application/octet-stream"
+		f.UpdateNginxConfigMapData("enable-brotli", "true")
+		f.UpdateNginxConfigMapData("brotli-types", contentEncoding)
+		f.UpdateNginxConfigMapData("brotli-min-length", strconv.Itoa(brotliMinLength))
+
+		f.EnsureIngress(framework.NewSingleIngress(host, "/", host, f.Namespace, framework.HTTPBinService, 80, nil))
+
+		f.WaitForNginxConfiguration(
+			func(server string) bool {
+				return strings.Contains(server, fmt.Sprintf("server_name %v", host)) &&
+					strings.Contains(server, "brotli on") &&
+					strings.Contains(server, fmt.Sprintf("brotli_types %v", contentEncoding)) &&
+					strings.Contains(server, fmt.Sprintf("brotli_min_length %d", brotliMinLength))
+			})
+
+		f.HTTPTestClient().
+			GET(fmt.Sprintf("/bytes/%d", brotliMinLength)).
+			WithHeader("Host", host).
+			WithHeader("Accept-Encoding", "br").
+			Expect().
+			Status(http.StatusOK).
+			ContentType(contentEncoding).
+			ContentEncoding("br")
+
+		f.HTTPTestClient().
+			GET(fmt.Sprintf("/bytes/%d", brotliMinLength-1)).
+			WithHeader("Host", host).
+			WithHeader("Accept-Encoding", "br").
+			Expect().
+			Status(http.StatusOK).
+			ContentType(contentEncoding).
+			ContentEncoding()
+	})
+})