Redirect from www to non-www with Kamal and Traefik

I recently wrote an article about how I do deployments with Kamal, which included setting up SSL for your domain and redirecting from non-ssl to ssl. In this article, I want to go further and show you how I redirect from www to non-www.

In the config folder, I have 3 yaml files: deploy.yml, deploy.development.yml, and deploy.production.yml.

deploy.yml contains the shared configuration across environments. Then, in each deploy.*.yml, I add the specifics for each environment, such as the server IP address, different domain names, etc.

# config/deploy.yml

# Name of your application.
service: my_app

image: username/image_name

  # Specify the registry server
  username: registry_username

    HOSTNAME: your_hostname
    - add_any_other_secrets_here

      - "443:443"
      - "/letsencrypt/acme.json:/letsencrypt/acme.json"
    entryPoints.web.address: ":80"
    entryPoints.websecure.address: ":443" websecure
    entryPoints.web.http.redirections.entryPoint.scheme: https
    entryPoints.web.http.redirections.entrypoint.permanent: true "your_email_address" "/letsencrypt/acme.json"
    certificatesResolvers.letsencrypt.acme.httpchallenge: true
    certificatesResolvers.letsencrypt.acme.httpchallenge.entrypoint: web

# add this if you are using a different route and port than kamal's defaults
  path: /
  port: 1234

Before you begin the deployment process for the first time, remember to create the necessary folders and Docker network:

  • Setup the directory for Let's encrypt

        mkdir -p /letsencrypt && touch /letsencrypt/acme.json && chmod 600 /letsencrypt/acme.json
  • create a Docker network called private

        docker network create -d bridge private

    The reason we're using a Docker private network is that when you open a port in Kamal, it becomes accessible to everyone. This happens because Docker automatically adds it to the iptables firewall.

Now let's see how config.development.yml looks like:

      - put_your_deployment_server_ip_address_here
      traefik.http.routers.devproject_secure.entrypoints: websecure
      traefik.http.routers.devproject.rule: Host(`your_domain.tld`) || Host(`www.your_domain.tld`)
      traefik.http.routers.devproject_secure.rule: Host(`your_domain.tld`) || Host(`www.your_domain.tld`)
      traefik.http.routers.devproject_secure.tls: true
      traefik.http.routers.devproject_secure.tls.certresolver: letsencrypt
      traefik.http.routers.devproject_secure.middlewares: redirect-to-non-www
      traefik.http.middlewares.redirect-to-non-www.redirectregex.regex: ^https?://www.your_domain.tld/(.*)
      traefik.http.middlewares.redirect-to-non-www.redirectregex.replacement: https://your_domain.tld/$1
      traefik.http.middlewares.redirect-to-non-www.redirectregex.permanent: true

  multiarch: false

Accepting traffic from domain and subdomain

To accept traffic from both the www subdomain and our main domain in our router, we can simply use || (OR) to include both hosts. By doing this, we'll accept incoming traffic from both and also obtain an SSL certificate for them.


traefik.http.routers.devproject_secure.middlewares: redirect-to-non-www

We hook up a new middleware caled: redirect-to-non-www to our devproject_secure router.

Regex middleware to redirect www to non-www

traefik.http.middlewares.redirect-to-non-www.redirectregex.regex: ^https?://www.your_domain.tld/(.*)
      traefik.http.middlewares.redirect-to-non-www.redirectregex.replacement: https://your_domain.tld/$1
      traefik.http.middlewares.redirect-to-non-www.redirectregex.permanent: true

This part is straightforward: we specify the regex expression, define the replacement, and enable 301 redirects by setting it to true.

Now, you can deploy your app (or update your configuration), but don't forget to run kamal traefik reboot every time you change Traefik settings.

I began using Traefik when I started deploying with kamal, and I'm currently learning about what it can do and how.