---
url: /aphanite/deployment.md
description: Deploy Aphanite behind a reverse proxy with TLS for production use.
---

# Deployment

In theory, once you put Aphanite in its own folder and grant execution permissions, double-clicking
it should run.

However, since Aphanite itself does not implement TLS\[^1], and Minecraft requires Yggdrasil servers
to implement TLS, you need a reverse proxy[^2] to provide TLS externally.

\[^1]:
TLS, simply put, is what turns HTTP into HTTPS. Here's
[an article from Cloudflare](https://www.cloudflare.com/learning/ssl/transport-layer-security-tls/)
that explains this technology in simple terms.

[^2]: Here's

[an article from Cloudflare](https://www.cloudflare.com/learning/cdn/glossary/reverse-proxy/)
that explains what a reverse proxy is.

Additionally, since all players including the server owner need direct access to Aphanite, if it's
running on a local machine, you'll need a tunneling service\[^3] to make it accessible from the
outside.

\[^3]:
A tunneling service (also called "port forwarding" or "NAT traversal") creates a secure tunnel
from a public server to your local machine, allowing external users to access services running
on your local computer. Here's
[an article from Cloudflare](https://www.cloudflare.com/learning/network-layer/what-is-tunneling/)
explaining it.

This article covers how to get Aphanite running properly.

## Do I Need a Reverse Proxy and/or Tunneling Service?

> \[!TIP]

**TL;DR: Reverse proxy: YES; Tunneling: as needed.**

Note that most tunneling services already include reverse proxy functionality. For the purposes of
this document:

* **Tunneling service**: Any tool that can forward ports to the cloud.
* **Reverse proxy**: A tool that only listens on ports locally.

They play different roles in Aphanite:

* Reverse proxy: Wraps Aphanite's "bare" HTTP traffic with TLS, turning it into HTTPS.
  * TLS is mandatory because Minecraft won't accept a "bare" HTTP Yggdrasil server. From a security
    standpoint, bare HTTP means your communication can be easily tampered with.
* Tunneling: Makes Aphanite accessible on the public internet.
  * This is optional. Question: Does the machine running Aphanite have a dedicated public IP, or a
    way to map local ports to the internet (typically provided by your server host)? If yes, you
    don't need a tunneling service.

So the picture is clear — a reverse proxy is required; tunneling is optional.

## How to Configure Reverse Proxy / Tunneling {#port-forwarding}

Below are two approaches you can choose from.

### Cloudflare Tunnel (Reverse Proxy + Tunneling, requires a domain) {#cloudflare-tunnel}

[Cloudflare Tunnel](https://developers.cloudflare.com/tunnel/) is a **free** reverse proxy and
tunneling service provided by Cloudflare, the well-known CDN and network infrastructure provider.

**Prerequisite**: A domain name hosted on Cloudflare. If you don't have one, you can also try
Cloudflare Quick Tunnel (see below), though each time you restart, your Aphanite server address will
change.

#### Steps (with a domain)

1. Log into your Cloudflare account.
2. Click Sidebar → Networking → Tunnels.
3. Click "Create a tunnel" in the top right.
4. Enter any name you like.
5. Follow the on-screen instructions to install and start `cloudflared`.
6. Click "Add Route".
7. Select "Published Applications".
8. Enter the domain you want to deploy to and Aphanite's local URL (relative to the machine running
   `cloudflared`; typically `http://127.0.0.1:<port>`, where `<port>` is from
   [Configuration](/aphanite/configuration)).
9. Well done! Your Aphanite should now be accessible at your new domain.

#### Steps (without a domain)

The following uses Cloudflare Quick Tunnels. It assigns a random new domain each time you start, so
it's not recommended for permanent use.

1. [Download and install `cloudflared`](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/downloads/).
2. Run:

```bash
cloudflared tunnel --url http://localhost:3000
```

3. Copy the output domain.
4. Set it as `service.domain` in your config file. (See [Configuration](/aphanite/configuration))
5. Start Aphanite.
6. Your Aphanite should now be accessible at `xxx.trycloudflare.com`.

#### Get a Domain Free of Charge

> \[!NOTE]
>
> This method is not tested.

DigitalPlat offers a free domain service, and that should be enough for Minecraft hosting. Check out
details
[here](https://github.com/DigitalPlatDev/FreeDomain/blob/main/documents/tutorial/getting-started/index.md).

### Nginx (Reverse Proxy only, requires a public IP / port forwarding, requires a domain)

> \[!NOTE]
>
> This approach is relatively more difficult to configure. Unless you have special requirements
> (e.g., you want SSL certificates under your own control), [Cloudflare Tunnel](#cloudflare-tunnel)
> is recommended.

This section is intended for experienced operators. In short:

1. Obtain an SSL certificate. Use [Certbot](https://certbot.eff.org/) or
   [acme.sh](https://github.com/acmesh-official/acme.sh).
2. Configure the reverse proxy. Here's a sample Nginx reverse proxy configuration for reference.

```nginx {24}
server {
  listen 443 ssl;
  listen [::]:443 ssl;
  http2 on;
  listen 443 quic;
  listen [::]:443 quic;
  http3 on;

  server_name aphanite.yourdomain.com;

  ssl_certificate "/etc/letsencrypt/live/yourdomain.com/fullchain.pem";
  ssl_certificate_key "/etc/letsencrypt/live/yourdomain.com/privkey.pem";
  ssl_session_cache shared:SSL:1m;
  ssl_session_timeout 10m;
  ssl_ciphers PROFILE=SYSTEM;
  ssl_prefer_server_ciphers on;
  add_header Alt-Svc 'h3=":443"; ma=86400';
  ssl_early_data on;

  # Load configuration files for the default server block.
  include /etc/nginx/default.d/*.conf;

  location / {
    proxy_pass http://127.0.0.1:3000/; # Change this to Aphanite's local listening address.
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    client_max_body_size 1024M;
  }
}
```

3. `nginx -t && nginx -s reload`.

Alternatively, server management panels like aaPanel or CyberPanel provide one-click reverse proxy
configuration — if you have one installed, refer to their documentation.

## Persistent Operation

> \[!NOTE]
>
> The following content applies to Linux only.

If you're running Aphanite on a server, you'll likely want it to persist beyond your SSH session.
The following guide lets you create a Systemd unit file to manage Aphanite.

These steps are only relevant if you manage your server purely via SSH. If running on a desktop or
via a panel, you probably don't need this.

The following assumes your `aphanite` binary is at `/opt/aphanite` and named `aphanite`. Adjust
paths according to your setup.

### Creating a User

It's good practice to create a dedicated user for Aphanite to isolate its directory access and
prevent accidents.

```bash
sudo useradd -U -d /opt/aphanite -s /sbin/nologin aphanite
sudo chown -R aphanite:aphanite /opt/aphanite
```

### Writing a Systemd Unit File

Here's a sample unit file — modify as needed, paying attention to the service start command.

```ini {11}
[Unit]
Description=Aphanite Yggdrasil server
After=network.target

[Install]
WantedBy=multi-user.target

[Service]
Type=simple
User=aphanite
ExecStart=/opt/aphanite/aphanite --config /opt/aphanite/config.toml --verbose
WorkingDirectory=/opt/aphanite
Restart=on-failure
```

Place your unit file at `/etc/systemd/system/aphanite.service`, then run:

```bash
sudo systemctl daemon-reload
```

Systemd should now be aware of your unit file.

### SELinux Policy

> \[!NOTE]
>
> The following may not apply to your distribution. If you encounter errors, consider asking an AI.

If your distribution has SELinux enabled with an aggressive policy, you may need to label the
Aphanite binary with `bin_t` for Systemd to start it properly.

Check your SELinux status:

```bash
getenforce
```

If the output is `Enforcing`, you'll likely need the following steps:

```bash
# Remember to replace /opt/aphanite/aphanite with the **absolute** path to Aphanite on your server.
sudo semanage fcontext -a -t bin_t /opt/aphanite/aphanite
sudo restorecon -Rv /opt/aphanite/aphanite
```

### Starting the Service

Finally, use these commands to manage Aphanite:

```bash
sudo systemctl start aphanite       # Start the Aphanite service
sudo systemctl enable aphanite      # Enable Aphanite to start on boot
sudo systemctl enable --now aphanite # Enable on boot and start now
sudo systemctl stop aphanite        # Stop Aphanite
sudo systemctl disable aphanite     # Disable auto-start

systemctl status aphanite           # Check current status
journalctl -u aphanite              # View Aphanite logs
```

## Persistent Operation with Containers

> \[!NOTE]
>
> The following content applies to Linux only.

If you run Aphanite in a container, keeping it running persistently is quite simpler. The following
sections describe three approaches.

> \[!NOTE]
>
> The official image runs as the non-root user with UID 1000. Make sure the host directory you mount
> (e.g. `/opt/aphanite`) is writable by it — for the examples below,
> `sudo chown -R 1000:1000 /opt/aphanite` will do.

### Option 1: The `--detach` Flag

This is the simplest approach. Add the `-d`, or `--detach`, flag when starting the container, and
the container runtime will keep it running in the background.

```bash{2}
podman run \ # or docker
  -d \
  --name aphanite \
  -p 3000:3000 \
  -v "~/.aphanite:/app:Z" \
  quay.io/feniota/aphanite:latest
```

It may be better to add the `--restart unless-stopped` flag. This makes the container runtime
automatically restart the container if it fails unexpectedly, helping keep the service available.

```bash{2,3}
podman run \ # or docker
  -d \
  --restart unless-stopped \
  --name aphanite \
  -p 3000:3000 \
  -v "~/.aphanite:/app:Z" \
  quay.io/feniota/aphanite:latest
```

### Option 2: Podman Quadlet

Podman Quadlet is a systemd unit generator from Podman. It lets you manage Podman containers using
the same INI syntax as Systemd unit files.

The advantages of this approach are:

* Deep integration with the underlying system service manager (Systemd), providing greater
  reliability than starting containers manually.
* Declarative configuration files that are easy to manage and migrate.
* Systemd's log management features can be used to view container logs.
* No additional configuration is required for starting the container on boot.

It also has some clear disadvantages:

* This approach cannot be used with Docker.
* It is not suitable if you prefer to manage your server through a panel.
* You need to learn how to configure Systemd.

Here is an example Quadlet configuration file. It assumes that you store Aphanite's configuration
file and database under `/opt/aphanite`.

```ini
[Unit]
Description=Aphanite Yggdrasil Server
After=network-online.target

[Container]
Image=quay.io/feniota/aphanite:v0.1.1 # Using latest here is not recommended
PublishPort=3000:3000
Volume=/opt/aphanite:/app:Z

[Install]
WantedBy=multi-user.target
```

Save it as `/etc/containers/systemd/aphanite.container`, then run:

```bash
sudo systemctl daemon-reload
```

Podman Quadlet will automatically generate `aphanite.service` from this configuration file, and you
can manage it like a Systemd service. For example:

```bash
sudo systemctl start aphanite # Start Aphanite
sudo systemctl stop aphanite # Stop Aphanite
sudo systemctl status aphanite # Check Aphanite's status
sudo journalctl -u aphanite # View Aphanite's logs
sudo journalctl -fu aphanite # View Aphanite's live logs
```

After starting the Aphanite service, Systemd automatically invokes Podman to download and start the
container. If you need to modify Aphanite's configuration file later, you may need to restart the
service afterwards.

> \[!NOTE]
>
> Because the unit files generated by Podman Quadlet are treated as transient by systemd, you cannot
> configure them to start on boot directly with `enable` or `disable`. However, because the example
> above includes an `[Install]` section, Podman Quadlet automatically adds the service to
> `multi-user.target.wants`. In other words, Aphanite is already configured to start on boot after
> you run `systemctl daemon-reload`.

### Option 3: Writing a Systemd Unit File Manually

```ini
[Unit]
Description=Aphanite Yggdrasil Server
After=docker.service
Requires=docker.service

[Service]
TimeoutStartSec=0
Restart=always
ExecStartPre=-/usr/bin/docker stop %n
ExecStartPre=-/usr/bin/docker rm %n
ExecStartPre=/usr/bin/docker pull quay.io/feniota/aphanite:latest
ExecStart=/usr/bin/docker run --rm --name %n -p 3000:3000 -v /opt/aphanite:/app:Z quay.io/feniota/aphanite:v0.1.0
ExecStop=/usr/bin/docker stop %n

[Install]
WantedBy=multi-user.target
```

Save it as `/etc/systemd/system/aphanite.service`, then run:

```bash
sudo systemctl daemon-reload
```

You can then manage Aphanite like a Systemd service. See [the commands above](#systemd-commands) for
details.

This approach is less maintainable than Podman Quadlet. Therefore, it is not recommended unless you
can only use Docker, migrating to Podman would be difficult, and, even in such a situation, you
desperately need Systemd's features.
