There are more than a handful of ways to set up port 80 and 443 web ingress on a custom Kubernetes cluster. Specifically a bare metal cluster. If you are looking to experiment or learn on a non-production cluster, but something more true to production than minikube, I suggest you check out my previous article Production Hobby Cluster, a step-by-step guide for setting up a custom production capable Kubernetes cluster.
This article builds on the Production Hobby Cluster guide. The following closely follows the official deploy ingress installation guide with a few adjustments suitable for the Production Hobby Cluster, specifically the use of a DaemonSet rather than a Deployment and leveraging hostNetwork and hostPort for the Pods on our DaemonSet. There are quite a few ingress nginx examples in the official repository if you are looking for a more specific implementation.
By now you may be managing multiple clusters. kubectl is a great tool to use on your local workstation to manage remote clusters, and with little effort you can quickly point it to a new cluster and switch between them all day. Check out my article kubectl Context Multiple Clusters for a quick tutorial.
§2026 Update
Two big things changed under this post, and both matter before you follow it.
First, the controller it installs is retired. The community ingress-nginx project was wound down, with maintenance ending in March 2026 and no further security patches (the IngressNightmare CVEs in 2025 were part of the story). I cover that in more detail in “413 Request Entity Too Large”. If you are standing up something new, look at the Gateway API or another maintained controller instead. The DaemonSet-with-hostNetwork pattern below is still a perfectly good way to claim ports 80 and 443 on bare metal, and most maintained controllers and Gateway implementations support it, usually paired with MetalLB to provide a real LoadBalancer service. The mechanics translate; the specific manifests do not.
Second, the Ingress API itself moved on. The extensions/v1beta1 Ingress used in the example near the end of this post was removed in Kubernetes 1.22 (2021). The current API is networking.k8s.io/v1, and the spec changed shape: serviceName and servicePort became a structured service.name and service.port.number, pathType is now required, and you set ingressClassName instead of the old annotation. Here is that same route in the current form:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ok
labels:
app: ok
system: test
spec:
ingressClassName: nginx
rules:
- host: ok.la.txn2.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ok
port:
number: 8080
The manual, multi-gist install below (namespace, default backend, config maps, RBAC, DaemonSet) is dated too. By the end of its life, ingress-nginx shipped as a single manifest or a Helm chart. Treat the steps below as a 2018 snapshot of how the pieces fit together, useful for understanding, not for a fresh install.
Archived: the original 2018 post follows. The walkthrough below deploys the now-retired ingress-nginx using the removed
extensions/v1beta1Ingress API. It is kept for the archives and no longer reflects current practice. Use the 2026 Update above for the working setup.
§Namespace
Setup a new namespace called ingress-nginx
Create using the configuration:
kubectl create -f https://gist.githubusercontent.com/cjimti/591d65a6940a87e7136bf0f51f438088/raw/0c5db06855d285d8a8b5bac1bfa6c9ed64b00c3b/00-namespace.yml
§Default Backend
Next, create a Deployment and a Service for the ingress controller.
Create using the configuration:
kubectl create -f https://gist.githubusercontent.com/cjimti/78a8ce1be09a9e874f6af54a6c8e4714/raw/95b172435fbc2b4551daf375e19f569bd9cc3aec/01-default-backend.yml
§Ingress Nginx ConfigMap
Create an empty ConfigMap for ingress-nginx.
Create using the configuration:
kubectl create -f https://gist.githubusercontent.com/cjimti/dc2841651c68a463b8990e6ce2ddb0c8/raw/2d4af61ac416e7494dac37c2eaf8bb024a1306a2/02-empty-configmap.yml
§TCP Services ConfigMap
Create an empty ConfigMap for ingress-nginx TCP Services.
Create using the configuration:
kubectl create -f https://gist.githubusercontent.com/cjimti/66605e303591b61e1baa347547336f2c/raw/eb6d3a6d1c5d0a47e4105a21d73573cb8e844406/03-tcp-services-configmap.yaml
§UDP Services ConfigMap
Create an empty ConfigMap for ingress-nginx UDP Services.
Create using the configuration:
kubectl create -f https://gist.githubusercontent.com/cjimti/ddb750c825e42ffd398da4590d4b61f7/raw/88061f9096e11be3457967b3ad5be6c2a1dcf68e/04-udp-services-configmap.yaml
§RBAC - Ingress Roles and Permissions
Here we setup a ServiceAccount named nginx-ingress-serviceaccount, a ClusterRole named nginx-ingress-clusterrole, a Role named nginx-ingress-role, a RoleBinding named nginx-ingress-role-nisa-binding and a ClusterRoleBinding named nginx-ingress-clusterrole-nisa-binding:
Create using the configuration:
kubectl create -f https://gist.githubusercontent.com/cjimti/b06886efc6313192282224d7c84c2151/raw/0c0c6501124c96d7229cafaeafe9f2a00db3fbea/05-rbac.yml
§DaemonSet
Creating a DaemonSet ensures that we have one Ingress Nginx controller Pod running on each node. Having an Ingress Controller on each node is crucial since we are using the host network and assigning the host ports 80 and 443 for HTTP and HTTPS ingress on each node. When adding a new node to the cluster, the DaemonSet ensures it gets an Ingress Nginx controller Pod.
Create using the configuration:
kubectl create -f https://gist.githubusercontent.com/cjimti/b9e820a18b06bd8a735b3b0676724826/raw/d4a0317cfe4ae4c4739d1d04e94c55b8d1426a98/06-ds.yaml
§Service
Add an ingress-nginx Service.
Create using the configuration:
kubectl create -f https://gist.githubusercontent.com/cjimti/d733ed08d59b3779233fb6edc175bb75/raw/a62765921f4fff395032d0e1f0a6db2cb773ab1c/07-service-nodeport.yaml
§Test
Make sure the default-http-backend pod and nginx-ingress-controller controller pods are running, the nginx-ingress-controller should be running on each node.
kubectl get pods -n ingress-nginx -o wide
# example output
NAME READY STATUS RESTARTS AGE IP NODE
default-http-backend-5c6d95c48-wbvw9 1/1 Running 0 1d 10.42.0.0 la2
nginx-ingress-controller-v44xz 1/1 Running 0 1d 45.77.71.39 la2
nginx-ingress-controller-wbb52 1/1 Running 0 1d 149.28.77.205 la3
nginx-ingress-controller-wjhcf 1/1 Running 7 1d 108.61.214.169 la1
Test each node by issuing a simple curl call:
# Example call
curl -v 45.77.71.39/
* Trying 45.77.71.39...
* TCP_NODELAY set
* Connected to 45.77.71.39 (45.77.71.39) port 80 (#0)
> GET / HTTP/1.1
> Host: 45.77.71.39
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Server: nginx/1.13.12
< Date: Thu, 17 May 2018 20:50:32 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 21
< Connection: keep-alive
<
* Connection #0 to host 45.77.71.39 left intact
default backend - 404
~
In this case, the nginx-ingress-controller Pod running on 45.77.71.39 responded adequately by passing the unknown route to the default-http-backend which correctly output a basic 404 page. Issue a curl call (or browse to them in a web browser) to each of your Nodes to test them.
§Add an Ingress
We are finally at a spot where we can start routing ingress to services. If you don’t already have a service to route to, I recommend using the txn2 ok service. ok is specifically designed to give useful information when testing Pod deployments.
§ok Deployment
Here we add a Deployment of ok with one replica.
Use the following command to add the Deployment:
kubectl create -f https://gist.githubusercontent.com/cjimti/bc293996ddcc3bf0cb9e5c3514ef1853/raw/18a26df0df3d239b679446af8b7b55f29d2271ba/00-ok-deployment.yml
§ok Service
Create an ok service to front-end our new ok Deployment above.
Use the following command to add the Service:
kubectl create -f https://gist.githubusercontent.com/cjimti/ae86bb7d3f777ac61e9ff9794ca52521/raw/b4f4bf26bf5526953bcc4e0c538887bfa7be1484/10-ok-service.yml
§ok Ingress
Finally, we have the easy task of creating an ingress route. The following is a minimal template since you need to point a domain name to your cluster. Note this uses the 2018 extensions/v1beta1 API, which was removed in Kubernetes 1.22; see the 2026 Update above for the current networking.k8s.io/v1 form.
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ok
labels:
app: ok
system: test
spec:
rules:
- host: ok.la.txn2.net
http:
paths:
- backend:
serviceName: ok
servicePort: 8080
path: /
I will go over https and managing certificates in future articles. For now you may want to checkout other ingress nginx examples.
§Port Forwarding / Local Development
Check out kubefwd for a simple command line utility that bulk forwards services of one or more namespaces to your local workstation.
If in a few days you find yourself setting up a cluster in Japan or Germany on Linode, and another two in Australia and France on vultr, then you may have just joined the PHC (Performance Hobby Clusters) club. Some people tinker late at night on their truck, we benchmark and test the resilience of node failures on our overseas, budget kubernetes clusters. It’s all about going big, on the cheap.
§Resources
- Kubernetes DaemonSet.
- deploy ingress on Kubernetes.
- Nginx ingress controller.
- Linode hosting.
- Digital Ocean hosting.
- Vultr hosting.
- Using KUBECONFIG for multiple configuration files.
- systemd help.
- Weave Net container networking.
- Etcd distributed keystore.
- WireGuard VPN.
- minikube
- Hobby Kube A fantastic write-up (with terraform scripts) and how I got started.
