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.
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
By the way, there are translations into various languages available in the web interface, and Russian is included as well.
For the VPN client cores, I use ready-made solutions from the internet rather than writing them from scratch; this helps avoid serious security issues and the like.
Are you about to introduce your work as possible official addon in the dev mailing list?
This is the usual way to supply something to the IPFire community.
BTW: Why do you think that solutions from the internet are more secure than self written source? The latter allows much more verification by the development crew.
You can also select the kernel used to load the configuration, and there is a kernel compatibility check.
Right now, I just need someone with a testing device.
My development environment doesn’t allow for full testing, since I’m doing everything inside a virtual machine; I also installed all the necessary libraries in a local repository to avoid issues with running zapret2.






