Contribute

Edit this documentation

Translate the documentation or open the complete project on your computer.

Nothing is published until you choose to submit your work.

Server Build

Run Wails applications as HTTP servers without a native GUI window

Wails v3 supports server mode, allowing you to run your application as a pure HTTP server without creating native windows or requiring GUI dependencies. This enables deploying the same Wails application to servers, containers, and web browsers.

Server mode is useful for:

  • Docker/Container deployments - Run without X11/Wayland dependencies
  • Server-side applications - Deploy as a web server accessible via browser
  • Web-only access - Share the same codebase between desktop and web
  • CI/CD testing - Run integration tests without a display server
  • Microservices - Use Wails bindings in headless backend services

Quick Start

Server mode is enabled via the server build tag. Your application code remains the same - you just build with the tag:

bash
# Using Taskfile (recommended)
wails3 task build:server
wails3 task run:server

# Or build directly with Go
go build -tags server -o myapp-server .

Here’s a minimal example:

go
package main

import (
    "embed"
    "log"

    "github.com/wailsapp/wails/v3/pkg/application"
)

//go:embed frontend/dist
var assets embed.FS

func main() {
    app := application.New(application.Options{
        Name: "My App",
        // Server options are used when built with -tags server
        Server: application.ServerOptions{
            Host: "localhost",
            Port: 8080,
        },
        Services: []application.Service{
            application.NewService(&MyService{}),
        },
        Assets: application.AssetOptions{
            Handler: application.AssetFileServerFS(assets),
        },
    })

    log.Println("Starting application...")
    if err := app.Run(); err != nil {
        log.Fatal(err)
    }
}

The same code can be built for desktop (without the tag) or server mode (with -tags server).

Configuration

ServerOptions

Configure the HTTP server with ServerOptions:

go
Server: application.ServerOptions{
    // Host to bind to. Default: "localhost"
    // Use "0.0.0.0" to listen on all interfaces
    Host: "localhost",

    // Port to listen on. Default: 8080
    Port: 8080,

    // Request read timeout. Default: 30s
    ReadTimeout: 30 * time.Second,

    // Response write timeout. Default: 30s
    WriteTimeout: 30 * time.Second,

    // Idle connection timeout. Default: 120s
    IdleTimeout: 120 * time.Second,

    // Graceful shutdown timeout. Default: 30s
    ShutdownTimeout: 30 * time.Second,

    // Additional origins allowed to open WebSocket connections and call
    // /wails/runtime. Same-origin requests are always allowed.
    WebSocketOriginPatterns: []string{"app.example.com"},

    // Disable WebSocket and runtime origin checks. Unsafe; default: false.
    WebSocketAllowAllOrigins: false,

    // TLS configuration (optional)
    TLS: &application.TLSOptions{
        CertFile: "/path/to/cert.pem",
        KeyFile:  "/path/to/key.pem",
    },
},

Features

Health Check Endpoint

A health check endpoint is automatically available at /health:

bash
curl http://localhost:8080/health
# {"status":"ok"}

This is useful for:

  • Kubernetes liveness/readiness probes
  • Load balancer health checks
  • Monitoring systems

Service Bindings

All service bindings work identically to desktop mode:

go
type GreetService struct{}

func (g *GreetService) Greet(name string) string {
    return "Hello, " + name + "!"
}

// Register in options
Services: []application.Service{
    application.NewService(&GreetService{}),
},

The frontend can call these bindings using the standard Wails runtime:

javascript
const greeting = await wails.Call.ByName('main.GreetService.Greet', 'World');

Events

Events work bidirectionally in server mode:

  • Frontend to Backend: Events emitted from the browser are sent via HTTP and received by your Go event handlers
  • Backend to Frontend: Events emitted from Go are broadcast to all connected browsers via WebSocket

Each browser tab is represented as a “window” with a unique name (browser-1, browser-2, etc.), accessible via event.Sender:

go
// Listen for events from browsers
app.Event.On("user-action", func(event *application.CustomEvent) {
    log.Printf("Event from %s: %v", event.Sender, event.Data)
    // event.Sender will be "browser-1", "browser-2", etc.
})

// Emit events to all connected browsers
app.Event.Emit("server-update", data)

From the frontend:

javascript
// Emit event to server (and all other browsers)
await wails.Events.Emit('user-action', { action: 'click' });

// Listen for events from server
wails.Events.On('server-update', (event) => {
    console.log('Update from server:', event.data);
});

Graceful Shutdown

The server handles SIGINT and SIGTERM signals gracefully:

  1. Stops accepting new connections
  2. Waits for active requests to complete (up to ShutdownTimeout)
  3. Runs OnShutdown hooks
  4. Shuts down services in reverse order

Differences from Desktop Mode

Feature Desktop Mode Server Mode
Native windows Created Browser windows (browser-N)
System tray Available Not available
Native dialogs Available Not available
Application menu Available Not available
Screen info Available Returns error
Service bindings Works Works
Events Works Works (via WebSocket)
Assets Via webview Via HTTP
CGO required Yes No

Window API Behavior

In server mode, window-related APIs are safely handled:

  • app.Window.NewWithOptions() - Logs a warning, returns nil
  • app.Hide() / app.Show() - No-op
  • app.Screen.GetPrimary() - Returns error

This allows code that references windows to run without crashing, though window operations have no effect.

Supported Platforms

Server mode is pure Go and compiles for every GOOS Wails supports, including android and ios, without CGO. On mobile targets it builds a headless server only:

  • The native mobile APIs, application.Android and application.IOS, are not available. Code that uses them must be excluded from server builds with a !server build constraint.
  • The default logger is the desktop one, which writes to stderr in development builds, rather than logcat or the WebView console.

Building for Production

Projects created with wails3 init include a build:server task:

bash
# Build for server mode
wails3 task build:server

# Build and run
wails3 task run:server

Manual Build

bash
# Build with server mode
go build -tags server -o myapp-server .

Docker

Wails projects include a ready-to-use Docker setup. To build and run your application in a container:

bash
# Build the Docker image
wails3 task build:docker

# Run it
wails3 task run:docker

That’s it! Your application will be available at http://localhost:8080.

You can customise the build with a few options:

bash
# Use a custom image tag
wails3 task build:docker TAG=myapp:v1.0.0

# Run on a different port
wails3 task run:docker PORT=3000

The generated Dockerfile.server creates a minimal image based on distroless. It handles the network binding automatically, so your application will be accessible from outside the container.

Docker Compose

For more complex deployments, here’s a Docker Compose configuration with health checks:

yaml
services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - WAILS_SERVER_HOST=0.0.0.0
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3

Custom Dockerfile

If you need more control, you can create your own Dockerfile. The key thing to remember is setting WAILS_SERVER_HOST=0.0.0.0 so the server accepts connections from outside the container:

dockerfile
# Build stage
FROM golang:alpine AS builder
WORKDIR /app
RUN apk add --no-cache git
COPY . .
RUN go mod tidy
RUN go build -tags server -ldflags="-s -w" -o server .

# Runtime stage
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
COPY --from=builder /app/frontend/dist /frontend/dist
EXPOSE 8080
ENV WAILS_SERVER_HOST=0.0.0.0
ENTRYPOINT ["/server"]

Security Considerations

When deploying server mode applications:

  1. Bind to localhost by default - Only use 0.0.0.0 when needed
  2. Use TLS in production - Configure ServerOptions.TLS
  3. Place behind reverse proxy - Use nginx/traefik for additional security
  4. Keep WebSockets and runtime calls same-origin - Add only trusted origins with WebSocketOriginPatterns; avoid WebSocketAllowAllOrigins
  5. Validate all inputs - Same security practices as any web application

Example

A complete example is available at v3/examples/server/:

bash
cd v3/examples/server

# Using Taskfile
task dev

# Or run directly
go run -tags server .

# Open http://localhost:8080 in browser

Environment Variables

For deployment scenarios where you need to override the server configuration without changing code, Wails recognises these environment variables:

Variable Description Default
WAILS_SERVER_HOST Network interface to bind to localhost
WAILS_SERVER_PORT Port to listen on 8080

These take precedence over the ServerOptions in your code, which is why the Docker examples set WAILS_SERVER_HOST=0.0.0.0 - it allows the container to accept external connections without requiring any changes to your application.

See Also

Runtime request size

Requests to /wails/runtime are limited to 64 MiB before JSON processing. An ordinary request above this limit receives HTTP 413, including requests without Content-Length. Chunked runtime uploads retain their separate 1 MiB per-chunk and 64 MiB assembled-payload limits. Use application middleware or a reverse proxy to impose a lower limit where appropriate.

Runtime request origin

/wails/runtime accepts only POST requests; other methods receive HTTP 405. A browser request whose Origin is neither the request host nor a match for WebSocketOriginPatterns receives HTTP 403, as does one without Origin whose Sec-Fetch-Site is cross-site or same-site. Requests carrying neither header, such as those from curl or other non-browser clients, are accepted. If a reverse proxy rewrites the Host header, add the public origin to WebSocketOriginPatterns.

WebSocketOriginPatterns exists for reverse-proxy deployments, where the public origin differs from the Host seen by the application. Frontends served from a different origin that call /wails/runtime directly are not supported: the endpoint emits no CORS headers and answers preflight OPTIONS requests with HTTP 405. Serve the frontend from the Wails server, or put both behind the same proxy origin.

Loopback Host check

When the server binds a loopback address (the default localhost, any 127.0.0.0/8 address, or ::1, whether set in ServerOptions.Host or WAILS_SERVER_HOST), every request must carry a Host header naming localhost, an IP literal, the bind host, or a host matching the host part of a WebSocketOriginPatterns entry; any other Host receives HTTP 403. This stops DNS rebinding, where a hostile web page points its own hostname at 127.0.0.1 so that its requests pass the same-origin checks above. The port is not compared, and spellings such as localhost. (trailing dot) or 127.1 are rejected. The check still applies when WebSocketAllowAllOrigins is set. If a local reverse proxy forwards its public Host, add that host to WebSocketOriginPatterns. Binds to other addresses, such as 0.0.0.0 in containers, are not affected.

Edit page

Last updated: