> For the complete documentation index, see [llms.txt](https://axiomemu.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://axiomemu.gitbook.io/docs/guides/manifest-remote-host.md).

# Remote Host for Manifest Generator

The Manifest Generator in AxiomEditor can upload client patches directly to your update server over SFTP. This guide walks through installing and configuring OpenSSH on the remote Windows host so that upload works out of the box.

{% hint style="info" %}
This guide covers the **remote host** (the machine that serves patches to players). You run AxiomEditor on your **local dev machine** - no SSH setup is needed there.
{% endhint %}

## Prerequisites

| Requirement | Details                                                                           |
| ----------- | --------------------------------------------------------------------------------- |
| OS          | Windows Server 2019+ or Windows 10 1809+ (OpenSSH is a built-in optional feature) |
| Access      | Administrator or Remote Desktop access to the host                                |
| Web server  | Nginx, Apache, IIS, or any HTTP server already serving your update folder         |
| Firewall    | Ability to open TCP port 22 (or your chosen SSH port)                             |

## 1. Install OpenSSH Server

OpenSSH ships as an optional Windows feature since Windows Server 2019 / Windows 10 1809. No third-party downloads needed.

{% tabs %}
{% tab title="PowerShell (recommended)" %}
Open PowerShell **as administrator**:

```powershell
# Check if OpenSSH Server is available
Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH.Server*'

# Install it
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
```

If the capability isn't listed (older builds), install manually from the [Microsoft OpenSSH releases](https://github.com/PowerShell/Win32-OpenSSH/releases).
{% endtab %}

{% tab title="Settings GUI" %}
**Settings -> Apps -> Optional Features -> Add a feature** -> search for **OpenSSH Server** -> **Install**.

On Windows Server: **Server Manager -> Manage -> Add Roles and Features -> Features -> OpenSSH Server**.
{% endtab %}
{% endtabs %}

## 2. Start and enable the SSH service

```powershell
# Start the service
Start-Service sshd

# Set it to start automatically on boot
Set-Service -Name sshd -StartupType Automatic

# Verify it's running
Get-Service sshd
```

You should see `Status: Running`.

## 3. Configure the SFTP subsystem

OpenSSH on Windows includes the SFTP subsystem by default. Verify it's enabled in the SSH config:

```powershell
notepad C:\ProgramData\ssh\sshd_config
```

Confirm this line exists and is **not** commented out:

```
Subsystem sftp sftp-server.exe
```

{% hint style="warning" %}
If the file doesn't exist yet, start the `sshd` service once and it will generate the default config at `C:\ProgramData\ssh\sshd_config`.
{% endhint %}

If you made any changes, restart the service:

```powershell
Restart-Service sshd
```

## 4. Open the firewall

The installer usually creates a firewall rule automatically. Verify it exists:

```powershell
Get-NetFirewallRule -Name *ssh*
```

If no rule exists, create one:

```powershell
New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server (sshd)' `
  -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
```

{% hint style="info" %}
If your host provider has an external firewall (Azure NSG, AWS Security Group, Hetzner firewall, etc.), open port 22 there too.
{% endhint %}

## 5. Set up a Windows user account

AxiomEditor authenticates with a username and password. The account needs read/write access to the update folder.

{% tabs %}
{% tab title="Use the existing Administrator" %}
If you already RDP in as `Administrator`, you can use those same credentials in AxiomEditor. No extra setup needed - skip to step 6.
{% endtab %}

{% tab title="Create a dedicated user" %}
Creating a separate user limits access scope:

```powershell
# Create user
net user axiom-upload YourStrongPassword /add

# Grant write access to the update folder
icacls "C:\nginx\html\muupdate" /grant "axiom-upload:(OI)(CI)F" /T
```

If you want this user to be able to create the folder structure on first upload, grant access to the parent directory instead.
{% endtab %}
{% endtabs %}

## 6. Verify SSH works

From your **local dev machine** (where AxiomEditor runs), open a terminal and test:

```bash
ssh Administrator@YOUR_HOST_IP
```

Accept the host key fingerprint when prompted. If you get a command prompt on the remote machine, SSH is working. Type `exit` to disconnect.

{% hint style="danger" %}
**If this step fails, AxiomEditor's upload will fail too.** Common issues:

* **Connection refused**: `sshd` service not running, or firewall blocking port 22.
* **Permission denied**: Wrong username or password.
* **Connection timed out**: External firewall (cloud provider) hasn't opened port 22.
  {% endhint %}

## 7. Configure AxiomEditor

Open AxiomEditor and go to the **Manifest Generator** tab. Fill in the Upload section:

| Field             | Value                                       | Example                  |
| ----------------- | ------------------------------------------- | ------------------------ |
| **Enable Upload** | Checked                                     |                          |
| **Host**          | Remote host IP or hostname                  | `62.84.187.28`           |
| **Port**          | SSH port                                    | `22`                     |
| **Username**      | Windows account on the host                 | `Administrator`          |
| **Password**      | Account password                            |                          |
| **Remote Path**   | Folder on the host where patches are served | `C:\nginx\html\muupdate` |
| **Streams**       | Concurrent upload connections (1-32)        | `16`                     |

Click **Save Config** to persist these settings to `manifest.ini` alongside AxiomEditor.

{% hint style="info" %}
The **Remote Path** should point to the directory your web server serves over HTTP. The client launcher downloads patches from this folder. For Nginx, this is typically a subdirectory under `html\`.
{% endhint %}

## 8. Set up Nginx to serve the update files

AxiomEditor uploads patch files to the remote host via SFTP, but players download them over HTTP. You need a web server on the remote host to serve the update folder. Nginx is the recommended option.

### Install Nginx

1. Download the latest Windows build from [nginx.org](http://nginx.org/en/download.html) (the "Stable" zip).
2. Extract to `C:\nginx-1.28.1` (or wherever you prefer).
3. Replace `conf\nginx.conf` with the configuration below.

### Nginx configuration

```nginx
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    tcp_nopush      on;
    tcp_nodelay     on;
    keepalive_timeout  65;

    # Increase output buffers for better throughput
    output_buffers 4 512k;
    postpone_output 1460;

    server {
        listen       80;
        server_name  _;

        # Frontend (React SPA)
        location / {
            root   C:/nginx-1.28.1/html/frontend/dist;
            index  index.html;
            try_files $uri $uri/ /index.html;
        }

        # Backend API proxy
        location /api {
            proxy_pass http://127.0.0.1:3001;
            proxy_http_version 1.1;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        # MU Auto-Updater - optimized for large file downloads
        location /update/ {
            alias C:/nginx-1.28.1/html/muupdate/;

            # Enable larger sendfile chunks
            sendfile_max_chunk 2m;

            # Aggressive caching for files (version is in manifest)
            add_header Cache-Control "public, max-age=31536000";

            # Disable caching for manifest only
            location = /update/manifest.json {
                alias C:/nginx-1.28.1/html/muupdate/manifest.json;
                add_header Cache-Control "no-cache, no-store, must-revalidate";
            }

            location = /update/version.dat {
                alias C:/nginx-1.28.1/html/muupdate/version.dat;
                add_header Cache-Control "no-cache, no-store, must-revalidate";
            }
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}
```

### Key points about this config

| Location   | What it does                                                      |
| ---------- | ----------------------------------------------------------------- |
| `/`        | Serves your website frontend (React SPA with client-side routing) |
| `/api`     | Proxies API requests to your backend on port 3001                 |
| `/update/` | Serves the update folder — this is what the client launcher hits  |

**Caching strategy**: All patch files under `/update/` are cached aggressively (1 year) because the client uses the manifest to decide what to download — file URLs effectively change when content changes. `manifest.json` and `version.dat` are explicitly set to **no-cache** so the launcher always gets the latest version info.

{% hint style="warning" %}
The `alias` path in the `/update/` block must match the **Remote Path** you configured in AxiomEditor. If AxiomEditor uploads to `C:\nginx-1.28.1\html\muupdate`, the alias must point there too.
{% endhint %}

### Create the update folder and start Nginx

```powershell
# Create the update folder if it doesn't exist
mkdir C:\nginx-1.28.1\html\muupdate

# Test the config for syntax errors
C:\nginx-1.28.1\nginx.exe -t

# Start Nginx
Start-Process -FilePath "C:\nginx-1.28.1\nginx.exe" -WorkingDirectory "C:\nginx-1.28.1"
```

### Make Nginx start on boot

Nginx on Windows doesn't install as a service by default. Use [NSSM](https://nssm.cc/) (Non-Sucking Service Manager) to wrap it:

```powershell
# Download nssm and place it somewhere in PATH, then:
nssm install nginx "C:\nginx-1.28.1\nginx.exe"
nssm set nginx AppDirectory "C:\nginx-1.28.1"
nssm start nginx
```

### Open port 80 in the firewall

```powershell
New-NetFirewallRule -Name nginx-http -DisplayName 'Nginx HTTP' `
  -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 80
```

### Verify it works

After AxiomEditor uploads a manifest, open a browser and navigate to:

```
http://YOUR_HOST_IP/update/manifest.json
```

You should see the JSON manifest with your file list and version number. If this works, the client launcher will be able to download patches.

## 8b. Alternative: Set up IIS to serve the update files

If you prefer IIS over Nginx (common on Windows Server where IIS is already installed), use this setup instead of step 8.

### Install IIS

{% tabs %}
{% tab title="PowerShell (recommended)" %}

```powershell
# Install IIS with static content serving
Install-WindowsFeature -Name Web-Server -IncludeManagementTools
```

{% endtab %}

{% tab title="Server Manager GUI" %}
**Server Manager -> Manage -> Add Roles and Features -> Server Roles -> Web Server (IIS)** -> check **Web Server** and **Management Tools** -> Install.

On Windows 10/11 (non-Server): **Settings -> Apps -> Optional Features -> More Windows Features -> Internet Information Services** -> check the box -> OK.
{% endtab %}
{% endtabs %}

### Create the update site folder

```powershell
mkdir C:\inetpub\muupdate
```

Set this as your **Remote Path** in AxiomEditor.

### Configure IIS

Open **IIS Manager** (`inetmgr`) and either add a virtual directory to the Default Web Site or create a new site.

{% tabs %}
{% tab title="Virtual directory (simplest)" %}

1. In IIS Manager, expand **Sites -> Default Web Site**.
2. Right-click -> **Add Virtual Directory**.
3. **Alias**: `update`
4. **Physical path**: `C:\inetpub\muupdate`
5. Click OK.

Players will access patches at `http://YOUR_HOST_IP/update/manifest.json`.
{% endtab %}

{% tab title="Dedicated site" %}

1. Right-click **Sites -> Add Website**.
2. **Site name**: `MuUpdate`
3. **Physical path**: `C:\inetpub\muupdate`
4. **Port**: `8080` (or 80 if no other site uses it)
5. Click OK.
   {% endtab %}
   {% endtabs %}

### Add a `web.config` for caching and MIME types

Create `C:\inetpub\muupdate\web.config`:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>

    <!-- Serve .dat and .json files correctly -->
    <staticContent>
      <remove fileExtension=".dat" />
      <mimeMap fileExtension=".dat" mimeType="application/octet-stream" />
      <remove fileExtension=".json" />
      <mimeMap fileExtension=".json" mimeType="application/json" />
      <remove fileExtension=".bmd" />
      <mimeMap fileExtension=".bmd" mimeType="application/octet-stream" />
      <remove fileExtension=".ozj" />
      <mimeMap fileExtension=".ozj" mimeType="application/octet-stream" />
      <remove fileExtension=".ozt" />
      <mimeMap fileExtension=".ozt" mimeType="application/octet-stream" />
      <remove fileExtension=".ozb" />
      <mimeMap fileExtension=".ozb" mimeType="application/octet-stream" />
      <remove fileExtension=".ozd" />
      <mimeMap fileExtension=".ozd" mimeType="application/octet-stream" />
      <remove fileExtension=".att" />
      <mimeMap fileExtension=".att" mimeType="application/octet-stream" />
      <remove fileExtension=".map" />
      <mimeMap fileExtension=".map" mimeType="application/octet-stream" />
    </staticContent>

    <!-- Aggressive caching for all patch files -->
    <httpProtocol>
      <customHeaders>
        <add name="Cache-Control" value="public, max-age=31536000" />
      </customHeaders>
    </httpProtocol>

    <!-- Directory browsing off -->
    <directoryBrowse enabled="false" />

  </system.webServer>

  <!-- No-cache for manifest.json and version.dat -->
  <location path="manifest.json">
    <system.webServer>
      <httpProtocol>
        <customHeaders>
          <remove name="Cache-Control" />
          <add name="Cache-Control" value="no-cache, no-store, must-revalidate" />
        </customHeaders>
      </httpProtocol>
    </system.webServer>
  </location>

  <location path="version.dat">
    <system.webServer>
      <httpProtocol>
        <customHeaders>
          <remove name="Cache-Control" />
          <add name="Cache-Control" value="no-cache, no-store, must-revalidate" />
        </customHeaders>
      </httpProtocol>
    </system.webServer>
  </location>

</configuration>
```

{% hint style="info" %}
IIS blocks unknown file extensions by default (returns 404). The `<staticContent>` block above registers all MU client file types so IIS serves them. Without this, players get download failures on `.bmd`, `.ozj`, `.ozt`, etc.
{% endhint %}

### Grant folder permissions

The IIS worker process (`IIS_IUSRS`) needs read access, and your SFTP upload user needs write access:

```powershell
# IIS read access
icacls "C:\inetpub\muupdate" /grant "IIS_IUSRS:(OI)(CI)RX" /T

# SFTP upload user write access (if using a dedicated user)
icacls "C:\inetpub\muupdate" /grant "axiom-upload:(OI)(CI)F" /T
```

### Open port 80 in the firewall (IIS)

IIS typically adds its own firewall rule during install. Verify:

```powershell
Get-NetFirewallRule -Name *iis* , *http* , *web*
```

If no rule exists:

```powershell
New-NetFirewallRule -Name iis-http -DisplayName 'IIS HTTP' `
  -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 80
```

### Verify IIS is serving updates

After AxiomEditor uploads a manifest:

```
http://YOUR_HOST_IP/update/manifest.json
```

You should see the JSON manifest. If you get a 404 on `.dat` or `.bmd` files, check that the `web.config` MIME types are in place.

## 9. Test the full pipeline

1. Set **Client Folder** to your compiled client output directory.
2. Set **Output Folder** to a local staging directory (e.g. `D:\manifest_output`).
3. Click **Generate Manifest**.
4. AxiomEditor runs 5 phases: scan, hash, copy, create manifest, upload.
5. Phase 5 (SFTP upload) connects to the remote host and uploads only changed files.

On the first run, all files are uploaded. Subsequent runs use delta sync - only files whose CRC32 hash changed since the last manifest are transferred.

## How the upload works

Understanding the upload mechanics helps troubleshoot issues:

1. AxiomEditor connects to the host via SFTP (SSH file transfer protocol).
2. It downloads the existing `manifest.json` from the remote path (if any).
3. It compares local vs remote file hashes.
4. Only changed or new files are uploaded. Deleted files are removed from the remote.
5. `manifest.json` and `version.dat` are always uploaded last.
6. Multiple SFTP connections run in parallel (controlled by the **Streams** setting).

## Troubleshooting

<details>

<summary><strong>Upload fails with "connection refused" or timeout</strong></summary>

* Verify `sshd` is running: `Get-Service sshd` on the remote host.
* Check Windows Firewall: `Get-NetFirewallRule -Name *ssh*`.
* Check external firewall (cloud provider panel).
* Test from your local machine: `ssh Username@Host -p Port`.

</details>

<details>

<summary><strong>Upload fails with "permission denied"</strong></summary>

* Double-check the username and password in AxiomEditor.
* Verify the user can log in via SSH: `ssh Username@Host`.
* If using a non-admin user, confirm it has write access to the remote path: `icacls "C:\nginx\html\muupdate"`.

</details>

<details>

<summary><strong>Upload succeeds but client launcher can't download patches</strong></summary>

* The remote path must match your web server's document root. Files uploaded via SFTP are just files on disk - the web server (Nginx/IIS/Apache) must be configured to serve that directory over HTTP.
* Check that `manifest.json` is accessible in a browser: `http://YOUR_HOST_IP/muupdate/manifest.json`.

</details>

<details>

<summary><strong>Upload is slow</strong></summary>

* Increase the **Streams** setting (up to 32) for more parallel connections.
* First upload transfers everything. Subsequent uploads are incremental (delta sync) and much faster.
* Check network bandwidth between your machine and the host.

</details>

<details>

<summary><strong>"Subsystem request failed" error</strong></summary>

The SFTP subsystem isn't enabled. Open `C:\ProgramData\ssh\sshd_config` on the remote host and ensure the `Subsystem sftp sftp-server.exe` line is uncommented. Restart `sshd` after editing.

</details>

## Security considerations

{% hint style="warning" %}
The password is stored in plain text in `manifest.ini` next to AxiomEditor. Keep this file on your dev machine only - do not distribute it with client or server bundles.
{% endhint %}

* Consider using a **dedicated user** with access only to the update folder, not the full Administrator account.
* If your host supports it, change the SSH port from the default 22 to reduce automated scan noise. Update the **Port** field in AxiomEditor to match.
* Disable SSH password authentication entirely once you've confirmed AxiomEditor works, and switch to key-based auth if your workflow supports it (advanced - requires manual SSH key setup outside AxiomEditor).
