Skip to content

Trino refuses a password over plain HTTP

Measured against a coordinator with authentication switched off, a basic auth header over plain HTTP still answers 401, so the connection is refused early.

A Trino password is not allowed for insecure authentication, and the coordinator says so in those words before it looks at anything else you sent. Not the catalog, not the statement, not whether the cluster has authentication configured at all. The rule belongs to the server, and every client meets it in the same place.

Everything below was measured against Apache Trino 476, the official trinodb/trino:476 image, on 2026-08-20.

What the coordinator does with a password over plain HTTP

Trino has no driver. A statement is the body of an HTTP request to POST /v1/statement, and the answer is read by following a chain of nextUri links. Default port 8080, which the client protocol shares with the web UI. There is no connection pooling and no session object: each statement is one stateless exchange.

So a password is not a handshake parameter. It is an Authorization: Basic header on an ordinary HTTP request, and the coordinator decides what to do with it before the request is a statement:

$ curl -s -i -H "Authorization: Basic $(printf 'user:password' | base64)" \
       -H 'X-Trino-User: libredb' --data-binary 'SELECT 1' \
       http://localhost:8080/v1/statement
HTTP/1.1 401 Unauthorized
Content-Type: text/plain;charset=utf-8
WWW-Authenticate: Basic realm="Trino"

Password not allowed for insecure authentication

The statement was valid. SELECT 1 needs no catalog and no schema. The credential is what was refused, and it was refused for the transport it arrived over.

Why authentication being disabled does not help

The measurement above was taken against a coordinator with authentication switched off entirely - the stock image, no password authenticator configured, no file realm, nothing to check a password against. It still answers 401.

That surprises people in a specific way. The reasoning goes: authentication is off, so credentials are ignored, so sending a spare username and password is harmless. It is not harmless. Sending a password over http:// breaks a connection that would otherwise have worked. Remove the password from the same connection and it connects, lists a catalog and runs statements.

The rule is not “the password is wrong”. It is that a password on a cleartext channel is a credential the coordinator will not accept into the process at all, and refusing it is the only handling that does not put it on the wire in front of whatever is between you and the cluster. Whether anyone would have verified it is beside the point.

This is worth separating from a second 401 the same endpoint produces, because they look alike and mean different things. A request that carries no identity at all gets:

$ curl -s -i --data-binary 'SELECT 1' http://localhost:8080/v1/statement
HTTP/1.1 401 Unauthorized
Content-Type: text/plain;charset=utf-8

Basic authentication or X-Trino-Original-User or X-Trino-User must be sent

Both are plain text, not JSON, which matters more than it sounds like it does. Anything the coordinator refuses before it becomes a statement answers in text; parse it as JSON and you raise a second, misleading error on top of the first one. Everything after that point is the opposite: a failed statement arrives as HTTP 200 with the failure inside the document. SELEKT 1, a missing table and an unsupported DDL all answer 200. The status line is consulted for exactly one thing on this engine, and this is it.

Refused in the client, before the round trip

LibreDB Studio does not send that request. The Trino transport constructor refuses the configuration up front, when the connection is created, with a message that names both ways out:

Trino refuses a password over plain HTTP. Enable TLS on the connection, or remove the password to connect as an unauthenticated user.

The reasoning is the same one that governs which controls appear per engine on the engines page: a round trip that can only end one way is not worth taking, and an error that arrives from the server carries less than a sentence written where the decision is made. A 401 in a connection dialog reads as a rejected credential. It sends people to look for the account, the realm, the group membership - none of which exist here. The configuration, not the credential, is what is wrong, and the remedy is two fields away.

When a 401 does come back from the coordinator - a real password authenticator rejecting a real password - it is categorised as auth and surfaces as an authentication error with the coordinator’s own wording kept verbatim. PERMISSION_DENIED reaches the same category by the other route: it is a statement failure, so it arrives inside a 200 with a Java stack attached, and that stack is dropped at the seam. Measured on the simplest possible typo, the part that is dropped is 19 frames and 3.3 KB, none of it about your query.

Connecting as an unauthenticated user instead

Against a cluster with authentication disabled, the working configuration is the short one. The coordinator’s host is the only field that has to be filled in:

Field Value
Host the coordinator
Port 8080 if left blank
Database the catalog to pin, for example tpch
Username optional, sent as X-Trino-User
Password leave empty on http://
SSL off

The Database field is a catalog, not a database. Trino’s hierarchy is catalog to schema to table, so the pinned catalog occupies the slot a PostgreSQL database would, and the tree below it is two levels deep with every table displayed schema.table. A connection that pins no catalog still connects and still runs fully qualified statements; what it cannot do is show a tree, and it says so rather than showing an empty one.

Here is the limit, stated plainly, because it is two limits and only one of them is obvious. A password is a TLS-only credential here: a connection carrying one over plain HTTP is refused before it is attempted, and a connection naming no user runs under a default identity rather than none. The user header is never omitted, because omitting it produces the second 401 above. A connection that names no user runs as libredb, and that is the name the coordinator’s own UI and system.runtime.queries will show against every statement it runs. If you are reading a query history to find out who ran something, an unauthenticated Trino connection has told you nothing. That is a property of the deployment, not of the client, and it is one of the reasons the security model treats the network the container sits on as part of the control set rather than an implementation detail.

Turning on TLS, and what that changes about the port

Enabling SSL on the connection selects https://, and the password becomes sendable in the same act. What it does not do is change the port.

8080 remains the default under TLS, and that is deliberate rather than an oversight. A secured Trino cluster listens wherever its operator put it - the coordinator’s own configuration decides - and inventing a well-known HTTPS port would send credentials to a number where nothing is listening. So the port is yours to type: whichever one your coordinator serves TLS on is the one the field wants. There is no connection string to paste this into either: jdbc:trino://host:port/catalog/schema is a real and widely used form, but the shared parser does not accept it, and offering a field that rejects everything pasted into it is worse than not offering the field.

The order of operations that follows from all of this is short. Confirm the coordinator serves TLS and on which port. Turn SSL on. Then add the password. Doing it the other way round produces a 401 that describes the transport and looks like it describes the account.