HomeLab – HTTPS for nginx.apps.lan (Traefik TLS) – Day 7

Goal: Enable HTTPS for nginx.apps.lan using a self-signed certificate, store it as a Kubernetes TLS Secret, configure Traefik to use it, and force HTTP→HTTPS redirects. All automation is done from the control host via Ansible; kubectl and helm run on the k3s master.

Reference environment: Kubuntu 25 host (fullstacklab.site) with SSH user stackadmin; k3s master 192.168.56.10, namespace apps (from Day 5), NGINX release web (from Day 6).

Day 7 — HTTPS for nginx.apps.lan (Traefik TLS)
Self-signed cert → TLS Secret → Traefik middleware → Helm upgrade

Step 1 — Create a self-signed cert and TLS Secret

We generate a one-year RSA cert with SAN and save it as nginx-tls in the apps namespace. The playbook includes a fallback for OpenSSL versions without -addext.

---
# ansible/tls_secret_on_master.yml
- name: Create self-signed cert and K8s TLS secret for nginx.apps.lan
  hosts: k3s_master
  become: true
  vars:
    kubeconfig: "/etc/rancher/k3s/k3s.yaml"
    tls_dir: "/tmp/tls-nginx-apps-lan"
    cn: "nginx.apps.lan"
    secret_name: "nginx-tls"
    namespace: "apps"
  environment:
    KUBECONFIG: "{{ kubeconfig }}"

  tasks:
    - name: Ensure openssl is installed
      package:
        name: openssl
        state: present

    - name: Ensure temp dir exists
      file:
        path: "{{ tls_dir }}"
        state: directory
        mode: "0755"

    - name: Try create self-signed cert with -addext (RSA 2048, 365d)
      shell: |
        openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
          -keyout {{ tls_dir }}/{{ cn }}.key \
          -out {{ tls_dir }}/{{ cn }}.crt \
          -subj "/CN={{ cn }}" \
          -addext "subjectAltName = DNS:{{ cn }}"
      args:
        creates: "{{ tls_dir }}/{{ cn }}.crt"
      register: openssl_addext
      failed_when: false

    - name: Fallback via openssl config (for systems without -addext)
      when: openssl_addext.rc != 0
      block:
        - name: Write minimal openssl config with SAN
          copy:
            dest: "{{ tls_dir }}/san.cnf"
            mode: "0644"
            content: |
              [ req ]
              distinguished_name = req_distinguished_name
              x509_extensions = v3_req
              prompt = no

              [ req_distinguished_name ]
              CN = {{ cn }}

              [ v3_req ]
              subjectAltName = @alt_names

              [ alt_names ]
              DNS.1 = {{ cn }}

        - name: Create self-signed cert using config
          shell: |
            openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
              -keyout {{ tls_dir }}/{{ cn }}.key \
              -out {{ tls_dir }}/{{ cn }}.crt \
              -config {{ tls_dir }}/san.cnf \
              -extensions v3_req
          args:
            creates: "{{ tls_dir }}/{{ cn }}.crt"

    - name: Create/Update TLS secret
      shell: |
        kubectl -n {{ namespace }} create secret tls {{ secret_name }} \
          --cert={{ tls_dir }}/{{ cn }}.crt \
          --key={{ tls_dir }}/{{ cn }}.key \
          --dry-run=client -o yaml | kubectl apply -f -

Step 2 — Traefik middleware for HTTP→HTTPS redirect

---
# ansible/traefik_https_middleware.yml
- name: Create Traefik HTTPS redirect middleware in apps ns
  hosts: k3s_master
  become: true
  vars:
    kubeconfig: "/etc/rancher/k3s/k3s.yaml"
    namespace: "apps"
    mw_name: "https-redirect"
  environment:
    KUBECONFIG: "{{ kubeconfig }}"

  tasks:
    - name: Apply Middleware manifest
      command: kubectl apply -f -
      args:
        stdin: |
          apiVersion: traefik.containo.us/v1alpha1
          kind: Middleware
          metadata:
            name: {{ mw_name }}
            namespace: {{ namespace }}
          spec:
            redirectScheme:
              scheme: https
              permanent: true

Step 3 — Helm upgrade: enable TLS + attach the middleware

Using a values file avoids brittle quoting for annotation keys that include slashes or dots.

---
# ansible/helm_upgrade_tls.yml
- name: Helm upgrade NGINX ingress with TLS and HTTPS redirect
  hosts: k3s_master
  become: true
  vars:
    kubeconfig: "/etc/rancher/k3s/k3s.yaml"
    release_name: "web"
    namespace: "apps"
    ingress_host: "nginx.apps.lan"
    tls_secret: "nginx-tls"
  environment:
    KUBECONFIG: "{{ kubeconfig }}"

  tasks:
    - name: Write Helm values (TLS + Traefik middleware)
      copy:
        dest: /tmp/web-values.yaml
        mode: "0644"
        content: |
          service:
            type: ClusterIP
          ingress:
            enabled: true
            ingressClassName: traefik
            hostname: {{ ingress_host }}
            tls: true
            extraTls:
              - hosts:
                  - {{ ingress_host }}
                secretName: {{ tls_secret }}
            annotations:
              "traefik.ingress.kubernetes.io/router.middlewares": "apps-https-redirect@kubernetescrd"

    # Optional pinning if specific image is required:
    # - name: Append image pin
    #   blockinfile:
    #     path: /tmp/web-values.yaml
    #     insertafter: EOF
    #     block: |
    #       image:
    #         repository: bitnamilegacy/nginx
    #         tag: 1.29.1-debian-12-r0

    - name: Helm upgrade with values
      command: >
        helm upgrade --install {{ release_name }} bitnami/nginx -n {{ namespace }}
        -f /tmp/web-values.yaml

    - name: Wait for deployment availability
      command: kubectl -n {{ namespace }} rollout status deploy/{{ release_name }}-nginx --timeout=180s

    - name: Show ingress details
      command: kubectl -n {{ namespace }} get ingress {{ release_name }}-nginx -o yaml

Step 4 — Validate from the control host

---
# ansible/https_validate_from_host.yml
- name: Validate HTTPS and redirect from control host
  hosts: localhost
  connection: local
  gather_facts: false
  vars:
    url_http: "http://nginx.apps.lan/"
    url_https: "https://nginx.apps.lan/"
  tasks:
    - name: Ensure /etc/hosts entry exists
      become: true
      lineinfile:
        path: /etc/hosts
        create: true
        state: present
        regexp: '^\S+\s+nginx\.apps\.lan\s*$'
        line: "192.168.56.10 nginx.apps.lan"

    - name: Check HTTP head (expect 301/308 to https)
      command: curl -I {{ url_http }}

    - name: Check HTTPS content (self-signed, so -k)
      command: curl -k -s {{ url_https }} | head -n 5

What’s next: swap the self-signed cert for mkcert (trusted locally) or try Let’s Encrypt (ACME staging) with Traefik. We’ll also practice Helm upgrade/rollback flow with a custom index page.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.