---
title: PhotoStructure for systemd
url: https://photostructure.com/server/systemd/
description: Interactive systemd service file generator for PhotoStructure for Node
date: 2026-03-18
keywords: Node.js, installation, Linux, settings, environment-variables, troubleshooting
---


[systemd](https://systemd.io/) can automatically start
[PhotoStructure for Node](/server/photostructure-for-node/) at boot, restart it
after crashes, and shut it down gracefully. Most modern
[Linux](/server/photostructure-for-node/#ubuntu-install) distributions
(Ubuntu, Fedora, Debian, Raspberry Pi OS) include systemd by default.

This wizard generates a service file tailored to your setup.

### Before you begin

Make sure you have:

- Completed [Steps 1–5](/server/photostructure-for-node/#ubuntu-install)
  (prerequisites, role user, Node.js, and PhotoStructure downloaded)
- Verified that `./server/photostructure` works when run manually
- The output of `id -u` and `id -g` from your terminal

For background on systemd concepts, see [Understanding and Administering
systemd](https://docs.fedoraproject.org/en-US/quick-docs/understanding-and-administering-systemd/index.html).

## Configure your service file

Fill in the form to generate a `photostructure.service` unit file.

<span class="cfg-required">\*</span> indicates required fields.

<div id="systemd-configurator" class="systemd-configurator">

<div class="cfg-form-header">
  <button type="button" id="cfg-reset" class="cfg-btn-reset"><svg xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 -960 960 960" width="16" fill="currentColor"><path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z"/></svg> Reset form</button>
</div>

<div class="cfg-section">
  <label class="cfg-label" for="cfg-user">Service user <span class="cfg-required">*</span></label>
  <p class="cfg-desc">
    The Linux user that runs PhotoStructure.
    This should be a dedicated role user, not root.
    See <a href="/server/photostructure-for-node/#ubuntu-post-setup">Step 3</a>.
  </p>
  <input type="text" id="cfg-user" class="cfg-input" required
         value="photostructure" placeholder="photostructure">
  <div class="cfg-field">
    <label class="cfg-label" for="cfg-group">Group</label>
    <p class="cfg-desc">
      Defaults to the same as the user. Only change if your photo directories
      are owned by a different group.
    </p>
    <input type="text" id="cfg-group" class="cfg-input"
           value="" placeholder="photostructure">
  </div>
</div>

<div class="cfg-section">
  <label class="cfg-label" for="cfg-install-dir">Install directory <span class="cfg-required">*</span></label>
  <p class="cfg-desc">
    The PhotoStructure installation directory
    (where you ran <code>git clone</code>).
  </p>
  <input type="text" id="cfg-install-dir" class="cfg-input" required
         value="/home/photostructure/photostructure-for-servers"
         placeholder="/home/photostructure/photostructure-for-servers">
  <p class="cfg-desc">
    ExecStart: <code id="cfg-exec-hint">/home/photostructure/photostructure-for-servers/server/photostructure</code>
  </p>
</div>

<div class="cfg-section">
  <label class="cfg-label">How did you install Node.js?</label>
  <p class="cfg-desc">
    systemd services don't run login scripts (<code>.bashrc</code>, <code>.profile</code>),
    so Node.js installed via a version manager won't be in <code>$PATH</code>.
    Select your method so the wizard can set the <code>NODE</code> environment variable.
  </p>
  <div class="cfg-radio-group">
    <label class="cfg-radio-label">
      <input type="radio" name="cfg-node-method" value="system" checked>
      <span class="cfg-radio-text">System package</span>
      <span class="cfg-radio-desc">— apt, dnf, or NodeSource</span>
    </label>
    <label class="cfg-radio-label">
      <input type="radio" name="cfg-node-method" value="nvm">
      <span class="cfg-radio-text">nvm</span>
      <span class="cfg-radio-desc">— Node Version Manager</span>
    </label>
    <label class="cfg-radio-label">
      <input type="radio" name="cfg-node-method" value="fnm">
      <span class="cfg-radio-text">fnm</span>
      <span class="cfg-radio-desc">— Fast Node Manager</span>
    </label>
    <label class="cfg-radio-label">
      <input type="radio" name="cfg-node-method" value="volta">
      <span class="cfg-radio-text">Volta</span>
    </label>
  </div>

  <div id="cfg-node-path-field" class="cfg-field cfg-hidden">
    <label class="cfg-label" for="cfg-node-path">Node.js binary path <span class="cfg-required">*</span></label>
    <p class="cfg-desc">
      Find your path by running <code>which node</code> as your PhotoStructure user.
      The startup script adds the containing directory to <code>PATH</code> automatically,
      so <code>npm</code> and <code>npx</code> will also be found.
    </p>
    <input type="text" id="cfg-node-path" class="cfg-input"
           value="" placeholder="">
    <div class="cfg-notice cfg-info-notice">
      After upgrading Node.js via your version manager, update this path and run
      <code>systemctl daemon-reload && systemctl restart photostructure</code>.
    </div>
  </div>
</div>

<div class="cfg-section">
  <div class="cfg-field">
    <label class="cfg-label" for="cfg-library">Library directory</label>
    <p class="cfg-desc">
      Where PhotoStructure stores your library database, previews, and organized
      copies of your photos. Leave empty to set it later via the web UI.
      See <a href="/faq/library/">library</a>.
    </p>
    <input type="text" id="cfg-library" class="cfg-input"
           value="" placeholder="/path/to/your/photo/library">
  </div>

  <div class="cfg-num-group">
    <div>
      <label class="cfg-label" for="cfg-port">Port</label>
      <p class="cfg-desc">HTTP port for the web UI. Default is 1787.</p>
      <input type="number" id="cfg-port" class="cfg-input"
             value="1787" min="80" max="65535" placeholder="1787">
    </div>
    <div>
      <label class="cfg-label" for="cfg-log-level">Log level</label>
      <p class="cfg-desc">See <a href="/guide/log-files/">log files</a>.</p>
      <select id="cfg-log-level" class="cfg-input">
        <option value="debug">debug (very verbose)</option>
        <option value="info">info (more detail)</option>
        <option value="warn" selected>warn (recommended)</option>
        <option value="error">error (errors only)</option>
        <option value="fatal">fatal (critical only)</option>
      </select>
    </div>
  </div>

  <label class="cfg-check-label">
    <input type="checkbox" id="cfg-expose">
    <span>Allow connections from other devices on my network</span>
  </label>
  <p class="cfg-desc">
    PhotoStructure for Node binds to localhost only by default.
    Check this to set <code>PS_EXPOSE_NETWORK_WITHOUT_AUTH</code>.
    PhotoStructure has no built-in authentication, so only expose on trusted networks.
    See <a href="/guide/remote-access/">remote access</a>.
  </p>
</div>

<details id="cfg-advanced" class="cfg-section">
  <summary class="cfg-label">Advanced options</summary>
  <div class="cfg-advanced-inner">
    <div class="cfg-field">
      <label class="cfg-label" for="cfg-env-file">Environment file</label>
      <p class="cfg-desc">
        Optional path to a file containing sensitive settings (e.g.
        <code>PS_LICENSE</code>). One <code>KEY=value</code> per line.
      </p>
      <input type="text" id="cfg-env-file" class="cfg-input"
             value="" placeholder="/etc/photostructure/photostructure.env">
    </div>
    <div class="cfg-field">
      <label class="cfg-label">Security hardening</label>
      <p class="cfg-desc">
        Basic hardening (<code>NoNewPrivileges</code>, <code>PrivateTmp</code>, etc.)
        is always enabled. Check this box to also enable filesystem restrictions.
      </p>
      <label class="cfg-check-label">
        <input type="checkbox" id="cfg-security">
        <span>Enable strict filesystem protection</span>
      </label>
      <div id="cfg-security-fields" class="cfg-hidden">
        <div class="cfg-notice cfg-info-notice">
          <code>ProtectSystem=strict</code> makes the filesystem read-only except for
          paths listed in <code>ReadWritePaths</code>.
          <code>ProtectHome=read-only</code> prevents writes to <code>/home</code>
          except where allowed. Make sure all paths below are correct before enabling.
        </div>
        <label class="cfg-label" for="cfg-rwpaths">ReadWritePaths</label>
        <p class="cfg-desc">
          Space-separated list of directories PhotoStructure needs write access to.
          Auto-populated from your settings above.
        </p>
        <input type="text" id="cfg-rwpaths" class="cfg-input"
               value="" placeholder="">
      </div>
    </div>
    <div class="cfg-field">
      <label class="cfg-label">Timeouts and restart</label>
      <p class="cfg-desc">
        These defaults work well for most systems.
        Increase <code>TimeoutStartSec</code> if bootstrap is slow (e.g. first run on a Pi).
      </p>
      <div class="cfg-num-group">
        <div>
          <label class="cfg-label" for="cfg-watchdog">WatchdogSec</label>
          <p class="cfg-desc">Seconds before a hung service is killed.</p>
          <input type="number" id="cfg-watchdog" class="cfg-input"
                 value="300" min="60" placeholder="300">
        </div>
        <div>
          <label class="cfg-label" for="cfg-timeout-start">TimeoutStartSec</label>
          <p class="cfg-desc">Seconds allowed for startup to finish.</p>
          <input type="number" id="cfg-timeout-start" class="cfg-input"
                 value="300" min="30" placeholder="300">
        </div>
        <div>
          <label class="cfg-label" for="cfg-timeout-stop">TimeoutStopSec</label>
          <p class="cfg-desc">Seconds allowed for graceful shutdown.</p>
          <input type="number" id="cfg-timeout-stop" class="cfg-input"
                 value="90" min="10" placeholder="90">
        </div>
        <div>
          <label class="cfg-label" for="cfg-restart-sec">RestartSec</label>
          <p class="cfg-desc">Seconds to wait between restart tries.</p>
          <input type="number" id="cfg-restart-sec" class="cfg-input"
                 value="10" min="1" placeholder="10">
        </div>
      </div>
    </div>
    <div class="cfg-field">
      <label class="cfg-label">Additional environment variables</label>
      <p class="cfg-desc">
        See PhotoStructure's <a href="/faq/environment-variables/">environment variables</a> for details.
      </p>
      <div id="cfg-env-vars"></div>
      <button type="button" id="cfg-add-env" class="cfg-btn-add">+ Add variable</button>
    </div>
  </div>
</details>

<div class="cfg-output">
  <h3>Your photostructure.service</h3>
  <pre id="systemd-output"><code class="language-ini"></code></pre>
  <div class="cfg-output-buttons">
    <button type="button" id="cfg-download" class="copy-compose-btn"><svg xmlns="http://www.w3.org/2000/svg" height="18" viewBox="0 -960 960 960" width="18" fill="currentColor"><path d="M480-320 280-520l56-58 104 104v-326h80v326l104-104 56 58-200 200ZM240-160q-33 0-56.5-23.5T160-240v-120h80v120h480v-120h80v120q0 33-23.5 56.5T720-160H240Z"/></svg> Download photostructure.service</button>
    <button type="button" id="cfg-copy" class="copy-compose-btn"><svg xmlns="http://www.w3.org/2000/svg" height="18" viewBox="0 -960 960 960" width="18" fill="currentColor"><path d="M360-240q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360Zm0-80h360v-480H360v480ZM200-80q-33 0-56.5-23.5T120-160v-560h80v560h440v80H200Zm160-240v-480 480Z"/></svg> Copy to clipboard</button>
    <button type="button" id="cfg-share" class="copy-compose-btn"><svg xmlns="http://www.w3.org/2000/svg" height="18" viewBox="0 -960 960 960" width="18" fill="currentColor"><path d="M240-80q-33 0-56.5-23.5T160-160v-400q0-33 23.5-56.5T240-640h120v80H240v400h480v-400H600v-80h120q33 0 56.5 23.5T800-560v400q0 33-23.5 56.5T720-80H240Zm200-240v-447l-64 64-56-57 160-160 160 160-56 57-64-64v447h-80Z"/></svg> Share configuration</button>
    <button type="button" class="cfg-btn-reset" onclick="document.getElementById('cfg-reset').click()"><svg xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 -960 960 960" width="16" fill="currentColor"><path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z"/></svg> Reset form</button>
  </div>
</div>

</div>

## Next steps

1. Create the library directory (if you set one above):

   <pre id="cfg-mkdir-cmd"><code class="language-sh"># Set a library directory above to generate this command</code></pre>

2. Create the service file by running:

   ```sh
   sudo systemctl edit photostructure.service --full --force
   ```

   Paste the generated unit file contents, save, and exit.

3. Reload and enable the service:

   ```sh
   sudo systemctl daemon-reload
   sudo systemctl enable photostructure.service
   ```

4. Start PhotoStructure:

   ```sh
   sudo systemctl start photostructure
   ```

5. Verify it's running:

   ```sh
   systemctl status photostructure
   ```

   Open <a id="cfg-open-link" href="http://localhost:1787">http://localhost:1787</a> to complete setup.

## Readiness and watchdog

The generated service file uses `Type=notify`, which means:

- **Readiness**: `systemctl start photostructure` blocks until the web server is
  actually listening. You'll see the status text update in `systemctl status`.
- **Watchdog**: PhotoStructure sends a keepalive ping every 2 minutes. If the web
  server becomes unresponsive for 5 minutes (`WatchdogSec=300`), systemd kills and
  restarts the service.
- **Shutdown**: When you run `systemctl stop`, PhotoStructure notifies systemd it's
  shutting down and gracefully closes its web and sync processes.

You can see the current status text with:

```sh
systemctl status photostructure
# Look for "Status:" line, e.g., "Status: Listening on port 1787"
```

## Control commands

| Action      | Command                                 |
| ----------- | --------------------------------------- |
| Start       | `sudo systemctl start photostructure`   |
| Stop        | `sudo systemctl stop photostructure`    |
| Restart     | `sudo systemctl restart photostructure` |
| Status      | `systemctl status photostructure`       |
| Logs        | `journalctl -u photostructure -f`       |
| Recent logs | `journalctl -u photostructure -n 50`    |

## Upgrading

Just restart the service. The startup script checks for new versions automatically:

```sh
sudo systemctl restart photostructure
```

## Troubleshooting

**1. Check the journal:**

```sh
journalctl -u photostructure -n 50
```

**2. Test running manually as the service user:**

```sh
sudo --login --user photostructure bash
cd ~/photostructure-for-servers
./server/photostructure
```

If this works but the systemd service doesn't, the issue is likely
environment-related (missing PATH, nvm not sourced).

**3. Common issues:**

| Symptom                                | Likely Cause        | Fix                                                                                                              |
| -------------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------- |
| Fails immediately (Duration: &lt;10ms) | Node.js not found   | Select your Node.js install method above                                                                         |
| "Permission denied" errors             | File permissions    | Ensure the service user owns the PhotoStructure directory; check `ReadWritePaths` if you enabled strict security |
| "Address already in use"               | Port conflict       | Another process is using port 1787, or PhotoStructure is already running                                         |
| Start operation timed out              | Slow bootstrap      | Increase `TimeoutStartSec` or run `server/photostructure --version` manually first                               |
| Watchdog timeout (limit 5min)!         | Web server hung     | Check `journalctl` for errors before the watchdog fired; check memory/disk                                       |
| "activating" for a long time           | Waiting for READY=1 | Bootstrap is still running (git pull, npm install) -- wait or check logs                                         |

**4. Port conflicts (`EADDRINUSE`):**

```sh
lsof -i :1787        # Find what's using the port
ss -tlnp | grep 1787 # Alternative

# Use a different port by changing the Port field above
```

**5. After making changes to the service file:**

```sh
sudo systemctl daemon-reload
sudo systemctl restart photostructure
```

## Uninstalling

```sh
sudo systemctl disable photostructure
sudo rm /etc/systemd/system/photostructure.service
sudo systemctl daemon-reload
```

## More information

- [PhotoStructure for Node](/server/photostructure-for-node/) -- full installation
  instructions for Ubuntu, Fedora, macOS, and Windows
- [Advanced settings](/getting-started/advanced-settings/) and
  [environment variables](/faq/environment-variables/)
- [User guide](/getting-started/user-guide/)

