Hello everyone, I’m currently trying to implement a VPN that uses the vless protocol. It doesn’t exist natively, so I’ll be making it as an add-on that can be managed via a web interface.
Here is the next version that has already been finalized. I need some help because I encountered one problem. I can upload it to git if necessary.
The problem is that when I stop, I get a strange 500 error in the web interface.
In the logs I see this when I try to stop
[Mon Mar 09 15:18:09.288324 2026] [cgid:error] [pid 23217:tid 23234] [client 192.168.50.115:61242] End of script output before headers: xray.cgi, referer: https://192.168.50.1:444/
[Mon Mar 09 15:18:14.813257 2026] [cgid:error] [pid 23217:tid 23233] [client 192.168.50.115:61243] End of script output before headers: xray.cgi, referer: https://192.168.50.1:444/
[Mon Mar 09 15:18:21.149383 2026] [cgid:error] [pid 23217:tid 23227] [client 192.168.50.115:61237] End of script output before headers: xray.cgi, referer: https://192.168.50.1:444/
[Mon Mar 09 15:42:20.011403 2026] [cgid:error] [pid 23217:tid 23241] [client 192.168.50.115:61306] End of script output before headers: xray.cgi, referer: https://192.168.50.1:444/
And the logs from the file that I made specifically /var/log/xray/xray-activate.log
[Mon Mar 9 15:42:19 2026] WARN: === STOP_ACTION START (uid=99, euid=99) === at /srv/web/ipfire/cgi-bin/user/xray.cgi line 232.
[Mon Mar 9 15:42:19 2026] WARN: → No PID file, searching with pgrep at /srv/web/ipfire/cgi-bin/user/xray.cgi line 249.
[Mon Mar 9 15:42:20 2026] WARN: → pgrep returned PID = 24944 at /srv/web/ipfire/cgi-bin/user/xray.cgi line 253.
[Mon Mar 9 15:42:20 2026] WARN: → Before kill TERM pid=24944 (process UID = nobody → direct kill) at /srv/web/ipfire/cgi-bin/user/xray.cgi line 262.
sub stop_action {
warn "=== STOP_ACTION START (uid=$>, euid=$<) ===";
my $pid = 0;
# 1. First, check the PID file (the most reliable)
if (-f $XRAY_PID) {
if (open(my $fh, '<', $XRAY_PID)) {
my $pid_str = <$fh> // '';
close $fh;
chomp $pid_str;
$pid = ($pid_str =~ /^\d+$/) ? int($pid_str) : 0;
warn "→ Read PID from file = $pid";
}
}
# 2. If there is no PID file, use pgrep (only one process!)
unless ($pid) {
warn "→ No PID file, search with pgrep";
my $pg = `pgrep -f '\\bxray\\b' 2>/dev/null | head -n1`;
chomp $pg;
$pid = ($pg =~ /^\d+$/) ? int($pg) : 0;
warn "→ pgrep returned PID = $pid";
}
if (!$pid) {
warn "→ Xray not found";
return { ok => false, message => "Xray not running" };
}
warn "→ Before kill TERM, pid=$pid (process UID = nobody → direct kill)";
my $signaled = kill 'TERM', $pid;
if ($signaled) {
warn "→ kill TERM sent successfully";
for (1..10) {
sleep 1;
last unless kill 0, $pid;
}
if (kill 0, $pid) {
warn "→ Process alive → KILL";
kill 'KILL', $pid;
}
warn "→ Delete PID file";
unlink $XRAY_PID;
} else {
warn "→ kill TERM FAILED (errno=$!)";
}
{
warn "→ Before firewall stop";
my $cmd = "/usr/bin/sudo -n /usr/local/bin/xray-firewall.sh stop 2>&1";
my $out = qx{$cmd};
my $rc = $? >> 8;
warn "→ firewall stop rc=$rc";
eval {
if (open my $lg, '>>:encoding(UTF-8)', $XRAY_ACTIVATE_LOG) {
chomp $out;
print $lg "[" . localtime() . "] stop firewall rc=$rc output=" . ($out ne '' ? $out : '<empty>') . "\n";
close $lg;
}
};
}
warn "=== STOP_ACTION COMPLETED ===";
return { ok => true, message => "Stopped xray + transparent OFF" };
}
Just in case, where I got the xray itself, here is the link GitHub - XTLS/Xray-core: Xray, Penetrates Everything. Also the best v2ray-core. Where the magic happens. An open platform for various uses. · GitHub

