diff --git a/s3api/admin-server.go b/s3api/admin-server.go
index 323f8c6b..65f288f6 100644
--- a/s3api/admin-server.go
+++ b/s3api/admin-server.go
@@ -122,6 +122,11 @@ func NewAdminServer(be backend.Backend, root middlewares.RootUserConfig, region
// initialize total requests cap limiter middleware
app.Use("*", middlewares.RateLimiter(server.maxRequests, nil, l))
+ // the same defaults the s3 server sets: CreateBucket validates its
+ // LocationConstraint against the region in the context, which would
+ // otherwise fall back to us-east-1 on a standalone admin port
+ app.Use("*", middlewares.SetDefaultValues(root, region))
+
app.Use("*", controllers.WrapMiddleware(middlewares.DecodeURL, l, nil))
// initialize the debug logger in debug mode
diff --git a/s3api/admin_create_bucket_test.go b/s3api/admin_create_bucket_test.go
new file mode 100644
index 00000000..28a58a1b
--- /dev/null
+++ b/s3api/admin_create_bucket_test.go
@@ -0,0 +1,118 @@
+// Copyright 2026 Versity Software
+// This file is licensed under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package s3api
+
+import (
+ "context"
+ "crypto/sha256"
+ "encoding/hex"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/aws/aws-sdk-go-v2/aws"
+ v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
+ "github.com/gofiber/fiber/v3"
+ "github.com/versity/versitygw/backend"
+ "github.com/versity/versitygw/s3api/controllers"
+)
+
+// The admin CreateBucket must check its LocationConstraint against the
+// gateway region on a standalone admin port, just like it does when the admin
+// API shares the s3 port.
+func TestAdminCreateBucketLocationConstraint(t *testing.T) {
+ const region = "eu-central-1"
+
+ servers := []struct {
+ name string
+ newApp func(t *testing.T) *fiber.App
+ }{
+ {name: "standalone admin server", newApp: func(t *testing.T) *fiber.App {
+ iam := testAdminIAM()
+ ctrl := controllers.New(backend.BackendUnsupported{}, iam, nil, nil, nil, false, false, "", 0)
+ srv := NewAdminServer(backend.BackendUnsupported{}, testAdminRoot, region, iam, nil, ctrl,
+ WithAdminConcurrencyLimiter(10, 10),
+ WithAdminQuiet(),
+ )
+ return srv.app
+ }},
+ {name: "admin on s3 server", newApp: func(t *testing.T) *fiber.App {
+ srv, err := New(backend.BackendUnsupported{}, testAdminRoot, region, testAdminIAM(), nil, nil, nil, nil,
+ WithConcurrencyLimiter(10, 10),
+ WithQuiet(),
+ WithAdminServer(),
+ )
+ if err != nil {
+ t.Fatalf("new s3 server: %v", err)
+ }
+ return srv.app
+ }},
+ }
+
+ tests := []struct {
+ name string
+ constraint string
+ wantStatus int
+ wantCode string
+ }{
+ // BackendUnsupported answers a CreateBucket that passed validation with 501.
+ {name: "gateway region", constraint: region, wantStatus: http.StatusNotImplemented},
+ {name: "no constraint", wantStatus: http.StatusBadRequest, wantCode: "IllegalLocationConstraintException"},
+ {name: "other region", constraint: "us-west-2", wantStatus: http.StatusBadRequest, wantCode: "IllegalLocationConstraintException"},
+ }
+
+ for _, srv := range servers {
+ for _, tt := range tests {
+ t.Run(srv.name+"/"+tt.name, func(t *testing.T) {
+ body := ""
+ if tt.constraint != "" {
+ body = "" + tt.constraint + ""
+ }
+
+ resp, err := srv.newApp(t).Test(signedAdminCreateBucketRequest(t, "/test-bucket/create", region, body))
+ if err != nil {
+ t.Fatalf("app.Test: %v", err)
+ }
+ defer resp.Body.Close()
+
+ respBody, _ := io.ReadAll(resp.Body)
+ if resp.StatusCode != tt.wantStatus {
+ t.Fatalf("status %d, want %d: %s", resp.StatusCode, tt.wantStatus, respBody)
+ }
+ if tt.wantCode != "" && !strings.Contains(string(respBody), ""+tt.wantCode+"") {
+ t.Fatalf("response %s, want error code %s", respBody, tt.wantCode)
+ }
+ })
+ }
+ }
+}
+
+func signedAdminCreateBucketRequest(t *testing.T, path, region, body string) *http.Request {
+ t.Helper()
+ sum := sha256.Sum256([]byte(body))
+ payloadHash := hex.EncodeToString(sum[:])
+
+ req := httptest.NewRequest(http.MethodPatch, "http://localhost"+path, strings.NewReader(body))
+ req.Header.Set("X-Amz-Content-Sha256", payloadHash)
+ req.Header.Set("X-Vgw-Owner", testAdminRoot.Access)
+ creds := aws.Credentials{AccessKeyID: testAdminRoot.Access, SecretAccessKey: testAdminRoot.Secret}
+ if err := v4.NewSigner().SignHTTP(context.Background(), creds, req, payloadHash, "s3", region, time.Now()); err != nil {
+ t.Fatalf("sign request: %v", err)
+ }
+ return req
+}