Linux Headless Mode | Twingate

Linux Headless Mode

Production Environments

The information in this article should be used as a guide only. If you are deploying this method into a production environment, we recommend that you also follow all security and configuration best practices.

Images and code samples in this guide may contain references to specific versions of software or container images that may not be the latest versions available. Please refer to the official documentation for that software or container image for the most up-to-date information.

Linux Headless Mode

The Twingate Client for Linux may be run in headless mode, allowing it to be used in environments where a graphical user interface is not available. This is particularly useful in server environments where the Client is required to run as a background service.

Twingate’s Linux Client may be used in either interactive mode or headless mode.

Prerequisites

You need a Twingate account with the necessary permissions to create and manage Services. See the Services documentation for more information on how to create a Service account and Service Keys.

Supported distributions

The Twingate Linux Client currently supports the following Linux distributions for x86/AMD64 and ARM64-based devices:

Additionally, the following are supported for x64/AMD64-based devices:

Twingate may work with other Linux distributions; however, we are only actively testing distributions listed here. The Twingate Client relies on systemd and glibc; distributions that include these packages are more likely to be compatible. Additionally, Twingate should work on upstreams of supported distributions (e.g. RHEL 10 is an upstream of Fedora 40 and the Client is known to work on it, but we do not test it).

The headless Linux Client can also be installed via a prebuilt Docker image.

Installing the Linux Client as a systemd service

The Linux Client is installed by following the standard installation process. More information on installation can be found on the main documentation page.

Once installed, configure the Linux Client to run in headless mode by running the twingate setup command with the --headless parameter:

curl https://binaries.twingate.com/client/linux/install.sh | sudo bash
sudo twingate setup --headless /path/to/service_key.json

Additional command line parameters, including the ability to set the default log level, are available. More information is available by running twingate help setup. Further documentation can be found in the main Linux Client docs page.

Starting & stopping the Client

# Start the client
sudo twingate start

# Check client status
twingate status

# Stop the client
sudo twingate stop

Troubleshooting

The Linux Client stores logs via journalctl. To learn more about interacting with logs, see the main Linux Client docs page.

Installing the Linux Client using Docker

Twingate offers a prebuilt Docker container for the headless Linux Client. This image contains the Linux Client running in the latest stable version of Debian. This may be useful in situations where only an individual service requires access to Twingate protected Resources.

Prior to running the Docker container, ensure that the Service Key is available on the host system. The Service Key must be mounted into the container using a volume.

Host Network Access in Headless Mode

The examples below assume other services or applications that need to access private, protected Resources are running in the same Docker network as the Client. If you need the Client to run in headless mode within a Docker container and capture all network traffic across all interfaces, including the host’s, you can use the --network host option in a docker run command or add network_mode: host to the service definition in your Docker Compose file.

Running the Client in Docker

The command below will run the Twingate Client in a Docker container, mounting the Service Key from the host system and starting the Client in headless mode. The command can be modified to suit the specific requirements of the environment, but the following parameters are required:

Without these two parameters the Client running inside of the container will not be able to connect to Twingate.

Further, when using the pre-built Twingate Client Docker image, the service key must be copied into /etc/twingate/service_key.json. This is done via the -v option with docker run or the volumes block with Docker compose.

Unsupported Compute Engines

Compute engines that do not support adding kernel capabilities to containers, such as AWS Fargate, are not supported at this time.

docker run -d \
  -v /path/to/service-key/:/etc/twingate/service_key.json \
  --device /dev/net/tun \
  --cap-add NET_ADMIN \
  twingate/client:latest

Alternatively, you can use Docker Compose to manage the container. An example docker-compose.yml file is shown below:

# Usage: SERVICE_KEY_PATH=<path> docker compose up -d

services:
  twingate-client:
    image: twingate/client:latest
    devices:
      - /dev/net/tun
    cap_add:
      - NET_ADMIN
    volumes:
      - ${SERVICE_KEY_PATH}:/etc/twingate/service_key.json
    restart: unless-stopped
    tty: true

Sharing networking stacks

If you want a service to be able to access protected Resources behind Twingate, you should set its network_mode to the Twingate Client service. This will allow the containers to share a networking stack and have traffic routed through Twingate.

The following examples creates a service for the Twingate Client and for Uptime Kuma, a common open source uptiming monitoring and alerting system. Uptime Kuma needs to be able to access protected Resources using the Twingate Client. The network_mode: "service:twingate-client" option allows the Uptime Kuma service to share its networking stack with the Twingate Client service, allowing it to access these Resources.

# Usage: SERVICE_KEY_PATH=<path> docker compose up -d

volumes:
  uptime-kuma:
    external: true

services:
  twingate-client:
    image: twingate/client:latest
    devices:
      - /dev/net/tun
    cap_add:
      - NET_ADMIN
    ports:
      - "3001:3001"
    volumes:
      - ${SERVICE_KEY_PATH}:/etc/twingate/service_key.json
    restart: unless-stopped

# Example external service that requires access to a protected Resource
  uptime-kuma:
    image: louislam/uptime-kuma:1
    container_name: uptime-kuma
    volumes:
      - uptime-kuma:/app/data
    # Share the networking stack with the Twingate Client
    network_mode: "service:twingate-client"
    depends_on:
      - twingate-client
    restart: always

Running as a Kubernetes Pod Sidecar

If you have a Kubernetes Pod that needs to be able to access protected Resources behind Twingate, you can run the Twingate Client as a sidecar to enable to Pod to route its traffic through Twingate.

When using the headless Linux Client within Kubernetes, you should use a secret to store the service key. In this example, a secret called twingate-service-key stores the service key into key.json (you can create it by running kubectl create secret generic twingate-service-key --from-file=key.json=/path/to/service_key.json):

apiVersion: v1
kind: Secret
metadata:
  name: twingate-service-key
data:
  key.json: <base64 encoded twingate service account key>

Then, within your deployment, you can add the Twingate Client as a sidecar. In this example, we’re running the twingate-sidecar as a sidecar to the app service. The twingate-sidecar service uses the twingate-service-key secret set up in the previous example. Note that similar to the examples above, the sidecar also needs NET_ADMIN and access to /dev/net/tun to successfully configure its networking stack.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
  labels:
    app: foo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: foo
  template:
    metadata:
      labels:
        app: foo
    spec:
      containers:
      - name: app
        image: alpine:3.12
      - name: twingate-sidecar
        image: twingate/client:latest
        securityContext:
          capabilities:
            add:
            - NET_ADMIN
            drop:
            - ALL
          privileged: true
          runAsGroup: 0
          runAsNonRoot: false
          runAsUser: 0
        volumeMounts:
        - mountPath: /dev/net/tun
          name: tun-device
        - mountPath: /etc/twingate/service_key.json
          name: twingate-service-key
          subPath: key.json
      volumes:
      - hostPath:
          path: /dev/net/tun
          type: CharDevice
        name: tun-device
      - name: twingate-service-key
        secret:
          secretName: twingate-service-key

Running the Twingate Client in CI/CD Jobs & Workflows

Twingate’s Headless Client can be flexibly integrated into existing CI/CD workflows. In addition to the above examples for systemd and docker, the following methods are commonly used by teams to enable private, protected access to Resources.

Using GitHub Actions

Twingate’s GitHub Action is a pre-defined step that integrates seamlessly into existing workflows to ensure the runner has access to private Resources (essentially installs as systemd like the example above).

Self-hosted Runners

Self-hosted runners can use either the systemd or docker methods to access Resources that are not directly routable from the runner’s subnet. This provides greater flexibility for private Resource access in CI/CD pipelines.

Containerized Jobs

In some cases, third-party services provide GitHub Actions or steps that spin up Docker containers on the runner to deploy infrastructure (e.g. via Terraform/Pulumi). Here’s how to ensure these containers can access private Resources through Twingate:

Shared Docker Network: If possible, run the container in the same Docker network as the Twingate Client, where the service in question uses the Twingate Client’s network stack, similar to the Docker Compose setup above.

When Sharing Networks Is Not Possible: Some services don’t allow multiple containers to share the same network namespace. In these cases, containers will typically use the host’s /etc/resolv.conf for DNS resolution unless explicitly overridden. This can cause DNS queries for private Resources to fail, as the container doesn’t know to forward DNS queries to the Twingate Client’s local DNS resolvers running on the host.

To ensure containers use Twingate’s DNS and route traffic correctly through Twingate’s tunnel, follow these steps:

Example:

- name: Configure Docker to Use Twingate DNS
  run: |
    # Step 1: Stop Docker service to make changes
    sudo systemctl stop docker

# Step 2: Create a temporary file to store the updated Docker configuration
    tmp=$(mktemp)

# Step 3: Add Twingate DNS resolvers to Docker's daemon configuration file
    sudo jq '. + { "dns": ["100.95.0.251", "100.95.0.252", "100.95.0.253", "100.95.0.254"] }' /etc/docker/daemon.json > "$tmp"

# Step 4: Move the updated configuration back to the Docker daemon file
    sudo mv "$tmp" /etc/docker/daemon.json

# Step 5: Restart Docker service to apply changes
    sudo systemctl start docker

# Step 6: Verify Docker is using the updated DNS resolvers
docker info | grep "DNS"

Other CI/CD platforms

As long as your CI/CD platform supports installing the Twingate Client in headless mode, allows underlying network configuration, and runs on a supported OS distro, you should be able to tunnel traffic to private Resources with relative success.