SMTP entrypoints returns HTTP 400 Error

Hello,

I’m trying to setup a dockerized mail server, with docker-mailserver behind Traefik.

The brief settings are like:

services:
  gateway:
    image: traefik:latest
    ports:
      # ...
      # Dashboard
      - 127.0.0.1:8080:8080
      # SMTP
      - 25:25
    #...
    command:
      # ...lot of other entrypoints
      - --entrypoints.smtp.address=:25
      # ...lot of other settings
    #...
  mail-service:
    image: docker.io/mailserver/docker-mailserver:latest
    #...
    labels:
      - "traefik.enable=true"
      # SMTP (25)
      - "traefik.tcp.services.mail_site-smtp.loadbalancer.server.port=25"
      - "traefik.tcp.routers.mail_site-smtp.rule=Host(`*`)"
      - "traefik.tcp.routers.mail_site-smtp.entrypoints=smtp"
      - "traefik.tcp.routers.mail_site-smtp.service=mail_site-smtp"
      #...other services & routers for IMAP/IMAPS/Submission, not yet dealed with
    #...

All HTTP routers are working, but not the mailserver.

It doesn’t receive mails, so I tried telnet:

$ telnet mail.xxx.site 25

Excepted response is a welcome such as:

220 kangli.site ESMTP Postfix

But nothing shows up, try EHLO:

EHLO 8.8.8.8

Get error:

HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8
Connection: close

400 Bad Request

Seems Traefik treated port 25 as an HTTP service, not SMTP. Looked into the entrypoint definition:

$ curl -s http://127.0.0.1:8080/api/entrypoints

The result contains:

{"address":":25","transport":{"lifeCycle":{"graceTimeOut":"10s"},"respondingTimeouts":{"readTimeout":"1m0s","idleTimeout":"3m0s"}},"forwardedHeaders":{},"http":{"sanitizePath":false,"maxHeaderBytes":1048576},"http2":{"maxConcurrentStreams":250},"udp":{"timeout":"3s"},"name":"smtp"}

I’m not sure if the ‘http’ and ‘http2’ properties matter, and have no idea where they come from. I’ve searched google and asked AI and can’t find the way out.

Any one have faced this issue? Any help would be very much appreciated, thank you!

I think for TCP entrypoint (doc) you need to use HostSNI(`*`)

That I’ve tried, it still work as HTTP. And I’ve also tried remove the the rule line. Neither way works.

Do you have any service that does not define entrypoints used? Then it would use the smtp entrypoint, too. That could be the reason it’s responding with http.

I do have such services, so I’ve fixed their routers.entrypoint mappings. Thus, I got smtp entrypoint only map to mail_kangli_site-smtp only.
Verified by command:

curl -s http://127.0.0.1:8080/api/tcp/routers | jq -r '.[] | "\(.service) - \(.entryPoints)"'
curl -s http://127.0.0.1:8080/api/http/routers | jq -r '.[] | "\(.service) - \(.entryPoints)"'

Only this line contains entrypoint smtp :

mail_kangli-site-smtp - ["smtp"]

So it suppose only used by mail-service

And it still work as HTTP:

$ telnet mail.kangli.site 25
Trying 124.71.132.154...
Connected to mail.kangli.site.
Escape character is '^]'.
ELHO 1.1.1.1
HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8
Connection: close

400 Bad RequestConnection closed by foreign host.

Detecting the entrypoints of routers:

curl -s http://127.0.0.1:8080/api/http/routers | jq -r '.[] | "\(.name) - \(.service) - \(.entryPoints)"'

I found one record work with noop@internal, which is:

web-to-websecure@internal - noop@internal - ["web"]

So I removed the redirection from HTTP to HTTPS:

      # - --entrypoints.web.http.redirections.entryPoint.to=websecure
      # - --entrypoints.web.http.redirections.entryPoint.scheme=https

And it works.

I would consider this as an issue of Traefik redirection feature, because it redirect all TCP connections, even it’s a SMTP connection.

But the redirection is only set on web entrypoint.

I don't get any html response when connecting via telnet example.com 9000 with the simple Traefik TCP example.

That’s strange, I can’t find the difference either.

However I prefer redirection rather than 404 on HTTP.

When the telnet works, what’s the output of below command?

curl -s http://127.0.0.1:8080/api/http/routers | jq -r '.[] | "\(.name) - \(.service) - \(.entryPoints)"'

What’s the settings of the line of web-to-websecure@internal?

curl -s http://127.0.0.1:8080/api/http/routers | jq -r '.[] | "\(.name) - \(.service) - \(.entryPoints)"'
mydashboard@docker - api@internal - ["websecure"]
web-to-websecure@internal - noop@internal - ["web"]
whoami@docker - whoami - ["websecure"]

curl -s http://127.0.0.1:8080/api/tcp/routers | jq -r '.[] | "\(.name) - \(.service) - \(.entryPoints)"'
tcpecho-le@docker - tcpecho-le - ["websecure"]
tcpecho-plain@docker - tcpecho-plain - ["tcp-plain"]

Have you looked at your Traefik dashboard?

What’s interesting is, when I restored the redirections settings, in order to re-produce the issue:

      - --entrypoints.web.http.redirections.entryPoint.to=websecure
      - --entrypoints.web.http.redirections.entryPoint.scheme=https

The issue disappeared.

$ telnet mail.kangli.site 25
Trying 124.71.132.154...
Connected to mail.kangli.site.
Escape character is '^]'.
220 mail.kangli.site ESMTP
^C
Connection closed by foreign host.

$ curl -v http://kangli.site
* Host kangli.site:80 was resolved.
* IPv6: (none)
* IPv4: 124.71.132.154
*   Trying 124.71.132.154:80...
* Connected to kangli.site (124.71.132.154) port 80
> GET / HTTP/1.1
> Host: kangli.site
> User-Agent: curl/8.5.0
> Accept: */*
> 
< HTTP/1.1 301 Moved Permanently
< Location: https://kangli.site/
< Date: Wed, 27 Aug 2025 13:35:20 GMT
< Content-Length: 17
< 
* Connection #0 to host kangli.site left intact
Moved Permanently

Which means, by remove & restore the redirections settings, it was fixed. I’ve no idea how I can re-produce this issue again, so I’ll close this topic.

Thank you very much @bluepuma77

This topic was automatically closed 3 days after the last reply. New replies are no longer allowed.