vaxilu/x-ui · warning

xray is already running

Error message

xray is already running

What it means

process.Start in xray/process.go returns this error when IsRunning() indicates an xray process is already alive. Starting a second instance would conflict on the stats API port and inbound ports, so Start refuses and records the situation via exitErr handling only on real failures. This is an idempotency guard on the lifecycle API.

Source

Thrown at xray/process.go:145

func (p *process) refreshVersion() {
	cmd := exec.Command(GetBinaryPath(), "-version")
	data, err := cmd.Output()
	if err != nil {
		p.version = "Unknown"
	} else {
		datas := bytes.Split(data, []byte(" "))
		if len(datas) <= 1 {
			p.version = "Unknown"
		} else {
			p.version = string(datas[1])
		}
	}
}

func (p *process) Start() (err error) {
	if p.IsRunning() {
		return errors.New("xray is already running")
	}

	defer func() {
		if err != nil {
			p.exitErr = err
		}
	}()

	data, err := json.MarshalIndent(p.config, "", "  ")
	if err != nil {
		return common.NewErrorf("生成 xray 配置文件失败: %v", err)
	}
	configPath := GetConfigPath()
	err = os.WriteFile(configPath, data, fs.ModePerm)
	if err != nil {
		return common.NewErrorf("写入配置文件失败: %v", err)
	}

View on GitHub (pinned to 9c1be8c57a)

Solutions

  1. Check IsRunning() before calling Start and skip if already running
  2. Use RestartXray which stops then starts, instead of raw Start
  3. If a stale process is holding state, stop it (kill the PID) and start again
  4. Investigate why the caller thought xray was stopped — check exitErr and logs

Example fix

// before
err := p.Start()
// after
if !p.IsRunning() {
    err = p.Start()
} // skip start if already running
Defensive patterns

Strategy: validation

Validate before calling

if p.IsRunning() {
    // skip Start, already running
    return nil
}
return p.Start()

Try / catch

if err := p.Start(); err != nil {
    if err.Error() == "xray is already running" {
        logger.Debug("xray already running; nothing to do")
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: Calling Start() while a previous xray instance is still running — e.g. calling RestartXray(false) logic paths that start without stopping first, or invoking Start manually after xray was already launched.

Common situations: Double-clicking restart in the panel; a script that calls start without checking status; a previous start succeeded but the caller believed it failed and retried; zombie process from an earlier crash keeping the port bound.

Related errors


AI-assisted analysis of vaxilu/x-ui@9c1be8c57a (2026-09-02). Data as JSON: /api/errors/a1ba6c1758525985. Report an issue: GitHub.