NebularStack
Website

Lunar - Installation

This guide takes a fresh server to a running Lunar instance. It is written to be followed top to bottom, and it keeps the steps as simple as possible. The Configure Lunar step below shows the complete config file with every option documented inline.

Supported platforms

Lunar runs on Ubuntu Linux only, and only on Long Term Support (LTS) releases. We do not support non-LTS (interim) releases, other Linux distributions, or other operating systems.

Our support policy is straightforward:

  • We support an Ubuntu LTS release for as long as it remains in standard support from Canonical. As older LTS releases reach end of life, our support for them ends with Canonical's.

  • We do not necessarily publish a Lunar build for the newest LTS the moment it is released. We qualify each new LTS ourselves first, and publish a build for it once we are satisfied it is stable for production. Until then, stay on the most recent LTS for which we already provide a build.

If you are unsure which release to target, use the latest LTS for which a Lunar package is available on the downloads page below.

Licensing

Lunar requires a valid license to run. This applies to every deployment, including integration work and proof-of-concept evaluations. There is no unlicensed mode.

We are happy to support evaluations: we issue trial licenses and extend them as needed while you build your integration or run a proof of concept. Request one, or ask for an extension, by emailing support@interstellio.io.

The license is bound to the specific machine it is issued for, so a license issued for one host will not validate on another. You request it using that machine's machine-id, which you obtain once Lunar is installed (see Request your license below). Alongside the license, the Interstellio team will provide the server_id, secret and subscriber endpoint your server needs.

Download

Download the latest Lunar package for your Ubuntu LTS release from the Interstellio downloads page:

https://www.interstellio.io/resources/downloads

The package is named lunar-<version>-ubuntu-<release>-<arch>.tgz. Choose the one that matches your Ubuntu LTS version and architecture.

Server requirements

Lunar should run on a dedicated server (physical, VM or KVM). Dedicated processor resources are important: they keep latency low and predictable so it never impacts RADIUS packet processing.

Minimum Requirements:

Prepare the server

Set the hostname

Edit /etc/hosts and set a fully qualified hostname for the server (replace lunar-1.domain.com with yours):

127.0.0.1 localhost
127.0.1.1 lunar-1 lunar-1.domain.com

Apply it:

sudo hostnamectl set-hostname lunar-1.domain.com

Raise system limits

Add the following to /etc/security/limits.conf so the lunar user is not capped by the defaults:

lunar  soft  nofile      2000000
lunar  hard  nofile      2000000
lunar  soft  memlock     unlimited
lunar  hard  memlock     unlimited

On a physical or VM/KVM host (not LXC), add the following to /etc/sysctl.conf:

fs.aio-max-nr=1048576
fs.nr_open=2097152
fs.inotify.max_queued_events=1048576
fs.inotify.max_user_instances=8192
fs.inotify.max_user_watches=1048576
kernel.keys.maxkeys=2000
kernel.threads-max=262144
kernel.keys.maxbytes=2000000
net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_fin_timeout=3
net.ipv4.tcp_max_syn_backlog=32768
net.ipv4.ip_local_port_range=11000 65535
net.ipv4.neigh.default.gc_thresh3=8192
net.ipv6.neigh.default.gc_thresh3=8192
net.core.netdev_max_backlog=250000
net.core.rmem_max=33554432
net.core.somaxconn=65535
vm.max_map_count=655300
vm.swappiness=0

These values raise kernel limits that a busy RADIUS server would otherwise hit, so the process can scale to high packet rates without the kernel throttling it. In short:

  • fs.* (aio-max-nr, nr_open, inotify.*): allow far more open file descriptors and async I/O slots per process, so Lunar can hold the many sockets and event handles a high-throughput server needs.

  • kernel.threads-max and kernel.keys.*: room for Lunar's worker thread pools and the kernel keyrings it uses, so thread and key creation never hits a ceiling under load.

  • net.ipv4.tcp_* and net.ipv4.ip_local_port_range: recycle closed connections quickly and widen the ephemeral port range, so many short-lived backend API connections do not exhaust available ports.

  • net.core.* (netdev_max_backlog, rmem_max, somaxconn) and the neigh.*.gc_thresh3 entries: deepen the network receive and accept queues and the ARP/neighbor tables, so bursts of RADIUS packets are absorbed instead of dropped.

  • vm.max_map_count and vm.swappiness=0: give the process plenty of memory mappings and keep it in RAM rather than swapping, which protects latency.

They are safe defaults for a dedicated Lunar server and rarely need adjusting.

Update and reboot

sudo apt update
sudo apt upgrade -y
sudo reboot

Install dependencies

Lunar ships as prebuilt binaries, so you only need the runtime libraries - no compiler, headers or build tools. Install nginx (used as the TLS proxy) and the runtime libraries:

sudo apt install -y \
    nginx \
    libjemalloc2 \
    libnghttp2-14 \
    librdkafka1 \
    libyaml-cpp0.8 \
    libssl3t64 \
    zlib1g

The msgpack C runtime library was renamed upstream, so its package name differs by release. On Ubuntu 22.04 and 24.04 LTS:

sudo apt install -y libmsgpackc2

On Ubuntu 26.04 LTS (the package moved to the universe component, enabled by default) use the hyphenated name instead:

sudo apt install -y libmsgpack-c2

Install Lunar

Create the lunar user

sudo useradd lunar -s /usr/sbin/nologin -d /opt/lunar

Unpack Lunar

Unpack the downloaded package into /opt/lunar so the tree lives at /opt/lunar/bin, /opt/lunar/etc and so on:

sudo mkdir -p /opt/lunar
sudo tar -xzf lunar-*-ubuntu-*.tgz -C /opt/lunar --strip-components=1

We strongly recommend installing under /opt/lunar. This is the standard location assumed by our documentation, the systemd service, the default config paths and support procedures. Installing elsewhere is possible but unsupported.

Install the systemd service

sudo ln -s /opt/lunar/etc/systemd/system/lunar.service /etc/systemd/system/lunar.service
sudo systemctl enable lunar

Request your license

The license is bound to this machine. Read the machine-id and send it to us:

/opt/lunar/bin/lunar -m

Email the machine-id to support@interstellio.io to request a license (mention if it is for a trial or proof of concept). In return you receive your license, and the server_id, secret and subscriber endpoint for your deployment.

Configure Lunar

Lunar is configured from a single YAML file, /opt/lunar/etc/lunar.yaml. It is read once at start-up, so restart lunar (sudo systemctl restart lunar) after changing it. The complete file is shown below, with every option documented inline. The values shown are the shipped defaults; to go live set at least license, server_id, secret and subscriber.endpoint to the values Interstellio provided, and keep services.subscriber set to true so the RADIUS service starts.

lunar:
  # Your license, which authorizes this server to run and unlocks the
  # services it is licensed for. lunar verifies it offline and binds it to
  # this machine, so a license issued for one host will not validate on
  # another.
  #
  # To request a license, contact the Interstellio IO team and provide this
  # machine's machine-id. Get it by running:
  #
  #     /opt/lunar/bin/lunar -m
  #
  # Paste the license you receive below. Without a valid license, licensed
  # services (e.g. subscriber/RADIUS) will not be authorized.
  license: <your_license>

  # Log verbosity: DEBUG, INFO, WARNING, ERROR or CRITICAL. Best practice:
  # INFO in production - DEBUG logs every packet and will flood under load.
  log_level: INFO

  # Optional text prepended to every log line, useful to tag the instance
  # when multiple lunar servers log to the same collector. Empty = no prefix.
  log_prefix: ""

  # Debug mode enables printing logs to stdout. Best practice:
  # False in production - it costs performance.
  debug_mode: False

  # Unique identity of this lunar server. Sent with every API request and
  # stamped into every Kafka event (source.server-id), so downstream
  # systems can tell which server handled the traffic. Provided by the
  # Interstellio IO team and unique per server. Do not change it.
  server_id: <your_lunar_server_id>

  # Shared secret used to authenticate this server to the backend API.
  # Provided by the Interstellio IO team and must match the secret
  # configured in the backend. Do not change it.
  secret: <your_lunar_secret_key>

  # Threads serving lunar's own management/API listener below. The default
  # is fine for most deployments; raise only if the management API itself
  # is under heavy use.
  api_threads: 32

  # Port and address of lunar's management/API listener. This listener may
  # face a public network; best practice is to run it behind a local nginx
  # reverse proxy (for TLS termination and access control) rather than
  # exposing it directly.
  api_port: 1337
  api_host: 127.0.0.1

services:
  # Which lunar services run in this process. The subscriber service is
  # the RADIUS engine. Set true to serve RADIUS. In the future we will
  # support many more.
  subscriber: true

subscriber:
  # Backend API that processes RADIUS requests (authentication, accounting
  # and CoA decisions are made there, not in lunar). Point this at your
  # nearest Interstellio region, or your own backend implementing the same
  # API.
  endpoint: https://<region-endpoint>

  # Verify the backend's TLS certificate. Best practice: True in production
  # with an https endpoint; False only for local testing.
  ssl_verify: True

protocol:
  radius:
    # Self-contained test mode. When true (with services.subscriber: true)
    # lunar starts a built-in TEST virtual on 127.0.0.1 with a client-test
    # client and secret testing123, and needs no backend - handy to prove
    # the install works before your backend is ready. It does NOT validate
    # credentials: every auth request is answered access-accept, so only
    # PAP is meaningful with radtest. CHAP, MS-CHAP, MS-CHAPv2 and EAP need
    # a real backend that runs the challenge/response, so they will not
    # authenticate here. For local verification only; never enable it in
    # production.
    testing: false

    # Interstellio RADIUS attribute dictionary shipped with lunar.
    dictionary: /opt/lunar/share/lunar/radius/dictionary.yaml

    # Maximum packets per second accepted per virtual server. 0 = unlimited.
    # Best practice: set a ceiling in production so one misbehaving
    # virtual radius cannot starve the others.
    virtual_rate_limit: 0

    # Depth of each internal processing queue. When a queue is full new
    # packets are dropped (the NAS retransmits, so brief bursts are safe).
    # Raise for high-throughput deployments; more depth = more memory and
    # more latency under sustained overload.
    queue_size: 8000

    # Seconds to wait for the backend API before giving up on a request
    # (maximum 5). Keep this BELOW the NAS retransmit timeout so lunar never
    # answers a request the NAS has already given up on.
    request_timeout: 2

    # Thread pools for the processing stages, in packet order:
    #   udp_listen_threads               - receive packets off the wire
    #   inbound_request_process_threads  - parse/validate, submit to the API
    #   api_client_threads               - drive HTTP requests to the API
    #   api_client_thread_inflight       - max concurrent API requests PER
    #                                      api_client thread
    #   outbound_request_process_threads - build replies from API responses
    #   udp_reply_threads                - send replies back to the NAS
    # Best practice: scale these together with CPU core count; the API
    # client threads and inflight limit set the ceiling on concurrent
    # backend requests (threads x inflight).
    #
    # You should not need to change these values. The defaults comfortably
    # sustain up to around 4000 requests in flight concurrently. If your
    # machine is already out of resources, raising these will not help and
    # will most likely make performance worse, because of the added thread
    # context switching.
    #
    # If your system has capacity for more load and you need help tuning
    # these values, contact our support at support@interstellio.io.
    udp_listen_threads: 2
    inbound_request_process_threads: 2
    api_client_threads: 4
    api_client_thread_inflight: 1000
    outbound_request_process_threads: 2
    udp_reply_threads: 2

    kafka:
      # Ship RADIUS events to Kafka. kafka is a LIST: every item is an
      # independent Kafka destination with its own topics, brokers, TLS,
      # SASL, tuning and its own producer threads and buffers. Every
      # event is sent to EVERY item that wants it, so a second item
      # replicates the events to a second Kafka cluster. The older
      # single-map form (the options below directly under kafka:, no
      # "-") is still accepted and behaves as one item without filters.
      #
      # Events are JSON with a common envelope: timestamp, event type,
      # source (hostname + server-id), metadata (virtual server id, name
      # and domain, client and NAS identities) and data (the RADIUS
      # record). Authentication and api-triggered-coa events pair the
      # request with its reply; accounting events carry the accounting
      # record. Events for the same user/station/session stay on one
      # partition, in order.
      #
      # Shipping is best-effort by design: RADIUS processing is never
      # delayed or blocked by Kafka. If events cannot be buffered they are
      # dropped and a warning is logged.
      -
        # Enabled per event type by setting its topic; requires at least
        # one broker. Remove a topic (or the brokers) to disable shipping
        # for that event type on this item.
        # authentication-topic: radius-authentication
        # accounting-topic: radius-accounting
        # Ship an event for every CoA/Disconnect the platform relays
        # through lunar's API, pairing the relayed request with the NAS
        # reply. Uses the same producer as the other streams of this
        # item.
        # api-triggered-coa-topic: radius-api-coa

        # Optional filters: only ship events of matching virtual servers
        # to this item. An omitted (or empty) filter matches every
        # virtual server; when both are set an event must match BOTH.
        # Matching ignores upper/lower case. A virtual server without a
        # domain never matches filter-domains (select it by id instead).
        # filter-virtual-ids:
        #   - 6F1D2C3B-0A1B-4C5D-8E9F-112233445566
        # filter-domains:
        #   - isp-a.co.za

        # Topics must already exist on the brokers. Lunar never creates
        # them and refuses broker-side auto-creation: producing to a
        # topic that does not exist fails per event (the event is dropped
        # and a warning is logged). Have your Kafka administrator
        # pre-create the topics with the right partitions, replication
        # and retention for your cluster.

        # Event buffering while brokers are slow or unreachable. Buffered
        # events are delivered once brokers recover; when a limit is
        # reached, new events are dropped (and a warning is logged)
        # rather than held back. buffer_events_count_limit is the maximum
        # buffered events; buffer_events_size_mb is the maximum buffered
        # megabytes - whichever fills first applies. Both limits are per
        # producer thread, per item. Best practice: size for the outage
        # you want to ride out - average events/second x seconds of
        # outage, with size_mb to match (events are roughly 2-8 KB each).
        # Memory is only consumed while brokers fall behind.
        buffer_events_count_limit: 1000000
        buffer_events_size_mb: 100

        # Threads producing to Kafka for this item. One comfortably
        # handles most workloads; raise only if a single thread cannot
        # keep up with your event rate. Note the buffer limits above are
        # per thread.
        producer_threads: 1

        # Delivery tuning. batch_size = max events sent to a broker in
        # one request; linger_ms = how long to wait for a batch to fill
        # before sending (higher = better throughput, more delay);
        # compression = none, gzip, snappy, lz4 or zstd. Best practice:
        # the defaults (10000 / 100 / lz4) suit most deployments; lower
        # linger_ms if consumers need events sooner.
        batch_size: 10000
        linger_ms: 100
        compression: lz4

        # TLS encryption to the brokers. Set ssl: True to encrypt all
        # Kafka traffic (the brokers must have TLS listeners). ssl_ca is
        # the CA certificate bundle used to verify the brokers (omit to
        # use the system CA store). If the brokers require client
        # certificates (mutual TLS), set ssl_cert and ssl_key.
        # ssl_verify: False disables broker certificate verification -
        # testing only. Best practice: always enable ssl when brokers are
        # reached over any shared network, and keep ssl_verify: True.
        # ssl: True
        # ssl_ca: /etc/lunar/kafka-ca.pem
        # ssl_cert: /etc/lunar/kafka-client.pem
        # ssl_key: /etc/lunar/kafka-client.key
        # ssl_verify: True

        # Username/password authentication (SASL), enabled by setting a
        # username. Enable ssl as well so credentials are encrypted in
        # transit. sasl_mechanism must match the brokers' setup: PLAIN,
        # SCRAM-SHA-256 or SCRAM-SHA-512 (the default). Best practice:
        # SCRAM-SHA-512 with ssl, and a dedicated Kafka user for lunar
        # with write access to these topics only.
        # username: lunar
        # password: secret
        # sasl_mechanism: SCRAM-SHA-512

        # Bootstrap brokers, used for the initial connection only: the
        # full cluster is discovered from whichever broker answers first,
        # and events are then delivered to the right brokers
        # automatically - including brokers not listed here. If a broker
        # fails, delivery moves to the remaining brokers on its own. Best
        # practice: list 2-3 brokers so startup survives one being down
        # (no need to list the whole cluster); durability against broker
        # loss comes from the topic's replication factor on the Kafka
        # side, not from this list.
        brokers:
          - localhost:9092

      # A second item replicates events to another cluster. Example: only
      # the accounting of virtual servers in isp-a.co.za, to isp-a's
      # Kafka.
      # -
      #   accounting-topic: isp-a-accounting
      #   filter-domains:
      #     - isp-a.co.za
      #   ssl: True
      #   brokers:
      #     - kafka.isp-a.co.za:9093

Lock the file down so only the lunar user can read it - it holds your secret and license:

sudo chown lunar:lunar /opt/lunar/etc/lunar.yaml
sudo chmod 600 /opt/lunar/etc/lunar.yaml

Replicating to several Kafka clusters

Every item of the kafka list is an independent Kafka destination with its own topics, brokers, TLS, SASL, tuning, producer threads and buffers. An event is sent to every item that wants it, so adding an item replicates the events to another cluster. Up to 32 items are supported.

An item can be limited to selected virtual servers with two optional filters:

filter-virtual-ids

Virtual server ids.

filter-domains

Virtual server domains.

Matching ignores upper/lower case. An event is sent to an item when the item has a topic for that event type and:

filter-virtual-ids

filter-domains

Event sent when

absent

absent

always (full copy)

set

absent

its virtual id is listed

absent

set

its domain is listed

set

set

both are listed

A virtual server without a domain never matches filter-domains; select it with filter-virtual-ids instead.

The examples below use these virtual servers:

Virtual id

Domain

AAA1

isp-a.co.za

AAA2

isp-a.co.za

BBB1

isp-b.co.za

CCC1

(none)

Example 1 - no filter: every event of every virtual server is copied to both clusters.

kafka:
  -
    accounting-topic: radius-accounting
    brokers:
      - kafka-main-1:9092
  -
    accounting-topic: radius-accounting
    brokers:
      - kafka-dr-1:9092

Example 2 - by domain: the main cluster gets everything; isp-a's cluster gets the accounting of AAA1 and AAA2 only.

kafka:
  -
    accounting-topic: radius-accounting
    authentication-topic: radius-authentication
    brokers:
      - kafka-main-1:9092
  -
    accounting-topic: isp-a-accounting
    filter-domains:
      - isp-a.co.za
    brokers:
      - kafka.isp-a.co.za:9093

Example 3 - by virtual id: ships BBB1 and CCC1. This is the only way to select a virtual server without a domain (CCC1).

kafka:
  -
    accounting-topic: radius-accounting
    filter-virtual-ids:
      - BBB1
      - CCC1
    brokers:
      - kafka-b-1:9092

Example 4 - both filters: ships AAA1 only. AAA2 is not in the id list, BBB1 is not in the domain, CCC1 matches neither.

kafka:
  -
    accounting-topic: radius-accounting
    filter-domains:
      - isp-a.co.za
    filter-virtual-ids:
      - AAA1
      - BBB1
    brokers:
      - kafka-a-1:9092

Each item adds its own producer threads (producer_threads plus librdkafka's own per-broker threads) and its own buffers, so size buffer_events_* per item.

Configure the TLS proxy

The management API listens locally on 127.0.0.1:1337. nginx sits in front of it to provide TLS. Generate a certificate (a self-signed one is fine to start; use a CA-signed certificate on a real FQDN for production):

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
    -keyout /etc/ssl/private/lunar.key \
    -out /etc/ssl/certs/lunar.crt

sudo chmod 640 /etc/ssl/private/lunar.key
sudo chown root:ssl-cert /etc/ssl/private/lunar.key

Replace /etc/nginx/sites-enabled/default with a proxy to Lunar:

upstream LUNAR {
    keepalive 128;
    keepalive_requests 128;
    keepalive_timeout 30;
    server 127.0.0.1:1337;
}

server {
    listen 443 default_server ssl;

    ssl_certificate /etc/ssl/certs/lunar.crt;
    ssl_certificate_key /etc/ssl/private/lunar.key;
    ssl_protocols TLSv1.2 TLSv1.3;

    root /var/www;

    location / {
        proxy_redirect off;
        proxy_http_version 1.1;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $host;
        proxy_set_header Connection "";
        proxy_buffering on;
        proxy_pass http://LUNAR;
    }
}

Start Lunar

Please ensure you have added license to config file before starting. Review Next Steps below.

sudo systemctl enable nginx
sudo systemctl restart nginx
sudo systemctl start lunar

Next steps

  • RADIUS Quickstart - get a virtual and client serving RADIUS end to end.

  • RADIUS Integration - the full reference for how lunar talks to your backend, and how to configure virtuals and clients.