Bayangkan satpam gedung yang mencatat tamu di buku besar. Suatu malam listrik padam sebentar, lalu satpam mencoret semua nama tamu sebagai “sudah pulang”. Padahal sebagian tamu masih duduk di ruang rapat dan terus melapor setiap lima menit. Satpam hanya menulis jam lapor mereka, tetapi lupa menghapus coretannya. Akibatnya, menurut buku, tamu itu sudah pulang sejak tadi malam. Kurang lebih begitulah yang terjadi pada tabel radacct FreeRADIUS ketika router MikroTik mengirim sinyal Accounting-On.
Di artikel ini kami membedah kasus nyata dari server billing ISP yang kami kelola. Pelanggan terlihat online di MikroTik, tetapi billing menandainya offline dengan keterangan “terakhir aktif 11 jam lalu”. Kami membahas cara kerja radacct, penyebab sesi tertutup palsu, query deteksi, perbaikan permanen pada queries.conf, dan aturan 15 menit untuk menyaring sesi hantu. Selain itu, kami menguji semua SQL di VPS uji memakai tabel radacct tiruan, dan screenshot berasal dari server asli yang sudah kami sensor.

Cara Billing Membaca Status Online dari radacct FreeRADIUS
Setiap kali pelanggan PPPoE login, MikroTik mengirim paket accounting ke FreeRADIUS, lalu FreeRADIUS menulisnya ke tabel radacct. Satu baris mewakili satu sesi. Oleh karena itu, billing cukup membaca tabel ini untuk mengetahui siapa yang sedang online, tanpa perlu bertanya ke router satu per satu.
| Kolom radacct | Isi | Kapan berubah |
|---|---|---|
acctstarttime | Waktu sesi mulai | Paket Start |
acctupdatetime | Laporan terakhir dari router | Setiap Interim-Update |
acctstoptime | Waktu sesi berakhir, NULL selama online | Paket Stop atau Accounting-On |
acctterminatecause | Alasan berhenti (User-Request, Lost-Carrier, NAS-Reboot) | Bersamaan dengan acctstoptime |
acctuniqueid | ID unik sesi, kunci pencarian saat update | Tetap |
Aturan dasarnya sederhana: sesi dianggap online selama acctstoptime IS NULL. Agar router mengirim laporan berkala, billing kami membuat skrip setup seperti ini, lalu setiap tenant menempelkannya di terminal MikroTik.
/radius add service=ppp,hotspot address=192.0.2.10 secret=RahasiaRadius timeout=2s comment="TBRadius - RADIUS Server" /radius incoming set accept=yes port=3799 /ppp aaa set use-radius=yes accounting=yes interim-update=5m
Baris pertama mendaftarkan server RADIUS, sedangkan baris kedua membuka port CoA 3799 agar billing bisa memutus sesi dari jauh. Kemudian baris ketiga menyalakan accounting PPP dengan interim-update=5m. Artinya, router melapor tiap lima menit untuk setiap sesi yang masih hidup. Angka lima menit ini nanti menjadi dasar aturan sesi hantu.
Empat Jenis Paket Accounting
Setiap paket accounting membawa atribut Acct-Status-Type. Berdasarkan nilai atribut itu, FreeRADIUS memilih blok query yang sesuai di queries.conf. Tabel berikut merangkum dampak setiap jenis paket terhadap baris radacct.
| Acct-Status-Type | Kapan router mengirim | Efek pada radacct |
|---|---|---|
| Start | Pelanggan berhasil login | INSERT baris baru, acctstoptime NULL |
| Interim-Update | Tiap 5 menit selama sesi hidup | UPDATE acctupdatetime dan counter byte |
| Stop | Pelanggan logout atau koneksi putus | Isi acctstoptime dan alasan berhenti |
| Accounting-On / Off | Router menyala atau mematikan accounting | Tutup SEMUA sesi terbuka milik NAS itu |
Tiga jenis pertama bekerja per sesi, sedangkan jenis keempat bekerja per router. Oleh karena itu, satu paket Accounting-On bisa mengubah puluhan baris sekaligus. Selama router benar-benar reboot, perilaku ini tepat, karena semua sesi lama memang sudah mati. Masalah baru muncul ketika sebagian sesi ternyata tetap hidup, seperti pada kasus berikut.
Contoh Kasus 1: Online di MikroTik, Offline di Billing
Pada 24 Oktober 2026, salah satu ISP pengguna billing kami melapor bahwa dua pelanggan tampil offline. Padahal di menu /ppp active MikroTik keduanya jelas online. Selain itu, kolom “terakhir aktif” menunjukkan 11 jam 30 menit, seolah-olah pelanggan putus sejak malam sebelumnya. Kami langsung membuka radacct di server FreeRADIUS.

Perhatikan dua baris teratas. Kolom acctstoptime berisi 23 Oktober pukul 15:20:40 dengan alasan NAS-Reboot. Namun, acctupdatetime justru berisi 24 Oktober pukul 02:50:40, sebelas jam lebih baru. Dengan kata lain, router terus mengirim interim untuk sesi itu sepanjang malam. Sesi yang benar-benar berhenti tidak mungkin terus melapor.
Query kedua menghitung kejadian serupa per NAS. Hasilnya ada tiga sesi terjebak, yaitu dua di router ISP tadi dan satu lagi di router tenant lain. Jadi, masalahnya bukan di satu router saja, melainkan di cara FreeRADIUS memproses accounting.
Akar Masalah: Accounting-On Menutup Semua Sesi
Saat MikroTik boot, router mengirim paket Accounting-On. Maknanya kira-kira “saya baru menyala, semua sesi lama dari saya sudah mati”. FreeRADIUS lalu menjalankan query berikut dari /etc/raddb/mods-config/sql/main/mysql/queries.conf.
accounting-on {
#
# Bulk terminate all sessions associated with a given NAS
#
query = "\
UPDATE ${....acct_table1} \
SET \
acctstoptime = ${....event_timestamp}, \
acctsessiontime = '${....event_timestamp_epoch}' \
- UNIX_TIMESTAMP(acctstarttime), \
acctterminatecause = '%{%{Acct-Terminate-Cause}:-NAS-Reboot}' \
WHERE acctstoptime IS NULL \
AND nasipaddress = '%{NAS-IP-Address}' \
AND acctstarttime <= ${....event_timestamp}"
}
Query ini menutup semua sesi terbuka milik NAS tersebut yang mulai sebelum waktu event. Syarat acctstarttime <= event_timestamp sebenarnya bertujuan melindungi sesi baru yang tersambung setelah router menyala. Namun, pada kasus kami, sesi baru tercatat mulai pukul 14:20:32, lebih awal dari Accounting-On pukul 15:20:40. Akibatnya, syarat pelindung itu tidak menolong.
Kami belum bisa memastikan kenapa jam start tercatat lebih awal. Dugaan terkuat adalah jam router yang belum sinkron NTP sesaat setelah boot. Apa pun sebabnya, sesi yang masih hidup ikut tertutup dengan alasan NAS-Reboot.
Interim-Update Bawaan Tidak Membuka Sesi Lagi
Setelah sesi tertutup, router tetap mengirim Interim-Update tiap lima menit. Sayangnya, query bawaan FreeRADIUS 3.0 hanya memperbarui waktu dan counter, seperti potongan berikut.
query = "\
UPDATE ${....acct_table1} \
SET \
acctupdatetime = (@acctupdatetime_old:=acctupdatetime), \
acctupdatetime = ${....event_timestamp}, \
acctinterval = ${....event_timestamp_epoch} - \
UNIX_TIMESTAMP(@acctupdatetime_old), \
framedipaddress = '%{Framed-IP-Address}', \
...
WHERE AcctUniqueId = '%{Acct-Unique-Session-Id}'"
Tidak ada satu baris pun yang menyentuh acctstoptime. Karena baris sesinya masih ada, UPDATE ini berhasil, sehingga FreeRADIUS juga tidak menjalankan query cadangan INSERT. Hasilnya, sesi tetap tertutup selamanya walaupun laporan terus masuk.
Simulasi di Lab dengan Tabel radacct Tiruan
Sebelum menyentuh server produksi, kami mereproduksi masalah ini di VPS uji. Anda cukup membutuhkan MariaDB atau MySQL dan file schema.sql bawaan FreeRADIUS. File itu ada di /etc/raddb/mods-config/sql/main/mysql/schema.sql pada Rocky Linux, atau di /etc/freeradius/3.0/mods-config/sql/main/mysql/ pada Debian dan Ubuntu.
mkdir -p ~/lab-radacct && cd ~/lab-radacct # salin schema.sql dari server FreeRADIUS ke folder ini, lalu: sudo mysql -e "CREATE DATABASE radius_lab" sudo mysql radius_lab < schema.sql sudo mysql radius_lab -e "SHOW TABLES"
Perintah ini membuat database terpisah bernama radius_lab, lalu membuat delapan tabel standar FreeRADIUS termasuk radacct. Selanjutnya, kami menyimpan setiap langkah simulasi sebagai file SQL bernomor dan menjalankannya dengan sudo mysql -t radius_lab < NAMA.sql.
Langkah 1: Isi Tiga Sesi Pelanggan
-- tiga pelanggan PPPoE di NAS 192.0.2.8, login 70 menit lalu, interim terakhir 12 menit lalu
INSERT INTO radacct (acctsessionid, acctuniqueid, username, nasipaddress, framedipaddress,
acctstarttime, acctupdatetime, acctinterval, acctterminatecause)
VALUES ('81900001', MD5('sesi-budi'), 'budi', '192.0.2.8', '10.3.0.11',
NOW() - INTERVAL 70 MINUTE, NOW() - INTERVAL 12 MINUTE, 300, ''),
('81900002', MD5('sesi-sari'), 'sari', '192.0.2.8', '10.3.0.12',
NOW() - INTERVAL 70 MINUTE, NOW() - INTERVAL 12 MINUTE, 300, ''),
('81900003', MD5('sesi-andi'), 'andi', '192.0.2.8', '10.3.0.13',
NOW() - INTERVAL 70 MINUTE, NOW() - INTERVAL 12 MINUTE, 300, '');
Kami memakai waktu relatif terhadap NOW() agar simulasi selalu masuk akal kapan pun Anda menjalankannya. Ketiga sesi masih terbuka karena kolom acctstoptime tidak kami isi.
Langkah 2: Kirim Accounting-On Tiruan
-- query accounting-on FreeRADIUS, variabel ${....event_timestamp} diganti @ev
SET @ev = NOW() - INTERVAL 10 MINUTE;
UPDATE radacct
SET acctstoptime = @ev,
acctsessiontime = UNIX_TIMESTAMP(@ev) - UNIX_TIMESTAMP(acctstarttime),
acctterminatecause = 'NAS-Reboot'
WHERE acctstoptime IS NULL
AND nasipaddress = '192.0.2.8'
AND acctstarttime <= @ev;
Query ini sama persis dengan versi FreeRADIUS. Bedanya, kami mengganti variabel template dengan @ev, yaitu waktu paket Accounting-On tiba 10 menit lalu. Setelah langkah ini, ketiga sesi tertutup dengan alasan NAS-Reboot.
Langkah 3: Interim-Update Versi Bawaan
-- interim-update BAWAAN untuk budi dan sari, 7 menit lalu (andi benar-benar putus)
SET @ev = NOW() - INTERVAL 7 MINUTE;
UPDATE radacct
SET acctupdatetime = (@old := acctupdatetime),
acctupdatetime = @ev,
acctinterval = UNIX_TIMESTAMP(@ev) - UNIX_TIMESTAMP(@old)
WHERE acctuniqueid IN (MD5('sesi-budi'), MD5('sesi-sari'));
SELECT username, acctupdatetime, acctstoptime, acctterminatecause FROM radacct;
Dalam skenario ini, budi dan sari sebenarnya tidak putus, sehingga router masih mengirim interim untuk mereka. Sebaliknya, andi benar-benar putus dan tidak melapor lagi. Hasil SELECT di screenshot lab di bawah membuktikannya: acctupdatetime budi dan sari lebih baru dari acctstoptime, tetapi status keduanya tetap tertutup. Inilah gejala yang sama persis dengan kasus produksi.
Mendeteksi Sesi yang Terjebak
Ciri sesi tertutup palsu sangat khas, yaitu ada laporan yang datang setelah waktu penutupan sesi. Query berikut mencarinya, dengan batas 20 menit agar hanya sesi yang masih aktif melapor yang muncul.
-- sesi yang ditutup, tapi masih menerima interim sesudahnya SELECT radacctid, username, nasipaddress, acctstoptime, acctupdatetime, acctterminatecause FROM radacct WHERE acctstoptime IS NOT NULL AND acctupdatetime > acctstoptime AND acctupdatetime > NOW() - INTERVAL 20 MINUTE;
Di lab, query ini mengembalikan budi dan sari, sedangkan andi tidak ikut karena interim terakhirnya lebih tua dari waktu penutupan. Batas 20 menit kami pilih karena interim datang tiap lima menit. Dengan demikian, sesi yang masih hidup pasti punya laporan dalam rentang itu.
Memperbaiki Data yang Sudah Terlanjur
Di produksi, kami lebih dulu menyimpan baris yang akan berubah ke file TSV sebagai backup. Setelah itu, kami membuka kembali sesinya dengan UPDATE yang memakai syarat yang sama.
sudo mysql -N radius -e "SELECT radacctid, username, nasipaddress, acctstoptime, acctterminatecause FROM radacct WHERE acctstoptime IS NOT NULL AND acctupdatetime > acctstoptime AND acctupdatetime > NOW() - INTERVAL 20 MINUTE" | tee ~/radacct_stuck_backup_20261024.tsv sudo mysql radius -e "UPDATE radacct SET acctstoptime = NULL, acctterminatecause = '' WHERE acctstoptime IS NOT NULL AND acctupdatetime > acctstoptime AND acctupdatetime > NOW() - INTERVAL 20 MINUTE; SELECT ROW_COUNT() AS dibuka;"
Perintah tee menampilkan hasil sekaligus menulisnya ke file. Kemudian UPDATE mengosongkan acctstoptime dan alasan berhentinya, lalu ROW_COUNT() melaporkan jumlah baris yang terbuka kembali. Saat itu hasilnya 3, dan kedua pelanggan langsung tampil online di billing. Namun, langkah ini hanya mengobati gejala, karena reboot router berikutnya bisa memicu masalah yang sama.
Perbaikan Permanen pada Query interim-update
Solusi permanennya adalah membuat Interim-Update membuka kembali sesi yang tertutup. Logikanya, jika laporan datang setelah waktu penutupan, berarti sesi itu jelas masih hidup. Untuk itu, kami menambahkan dua baris tepat di bawah acctupdatetime = ${....event_timestamp}, \ pada UPDATE pertama di blok interim-update.
acctstoptime = IF(acctstoptime IS NOT NULL AND acctstoptime < ${....event_timestamp}, NULL, acctstoptime), \
acctterminatecause = IF(acctstoptime IS NULL, '', acctterminatecause), \
Baris pertama mengosongkan acctstoptime hanya jika waktu penutupannya lebih tua dari interim yang baru datang. Jadi, sesi yang memang sudah berhenti dengan paket Stop tidak ikut terbuka. Selanjutnya, baris kedua menghapus alasan NAS-Reboot. Baris ini sengaja kami taruh sesudah baris pertama, karena MariaDB dan MySQL mengevaluasi SET pada UPDATE satu tabel dari kiri ke kanan. Akibatnya, saat baris kedua berjalan, acctstoptime sudah bernilai NULL.
Memasang Patch dengan Aman
Agar perubahan presisi dan bisa kami ulang, kami memakai skrip awk kecil. Skrip ini hanya menyisipkan dua baris pada kemunculan pertama di dalam blok interim-update, lalu gagal dengan pesan jelas jika polanya tidak ketemu.
cat > patch_interim.awk <<'EOF'
# Sisipkan dua baris "buka kembali sesi" ke UPDATE pertama di blok interim-update
BEGIN { inblk = 0; done = 0 }
/^[ \t]*interim-update[ \t]*\{/ { inblk = 1 }
{
print
if (inblk && !done && $0 ~ /acctupdatetime[ \t]*=[ \t]*\$\{\.\.\.\.event_timestamp\},[ \t]*\\$/) {
print "\t\t\t\t\tacctstoptime = IF(acctstoptime IS NOT NULL AND acctstoptime < ${....event_timestamp}, NULL, acctstoptime), \\"
print "\t\t\t\t\tacctterminatecause = IF(acctstoptime IS NULL, '', acctterminatecause), \\"
done = 1
}
}
END { if (!done) { print "PATCH-GAGAL" > "/dev/stderr"; exit 1 } }
EOF
Pola regex mencari baris acctupdatetime = ${....event_timestamp}, \ yang diakhiri backslash. Dengan demikian, baris pertama yang memakai @acctupdatetime_old tidak ikut cocok. Selain itu, flag done memastikan sisipan hanya terjadi sekali.
Terapkan, Validasi, dan Restart
Q=/etc/raddb/mods-config/sql/main/mysql/queries.conf B=$Q.bak-20261024-interim sudo cp -a $Q $B sudo awk -f patch_interim.awk $Q > /tmp/queries.new sudo diff $B /tmp/queries.new sudo cp /tmp/queries.new $Q sudo chown --reference=$B $Q sudo chmod --reference=$B $Q sudo radiusd -XC # harus berakhir: Configuration appears to be OK sudo systemctl restart radiusd
Pertama, cp -a membuat backup lengkap dengan izin aslinya. Kemudian awk menulis hasil ke file sementara, dan diff menampilkan dua baris tambahan untuk kita periksa. Setelah file tersalin, chown dan chmod mengembalikan pemilik root:radiusd. Terakhir, radiusd -XC memeriksa sintaks sebelum restart, sehingga typo tidak mematikan autentikasi semua pelanggan. Di Debian dan Ubuntu, ganti radiusd dengan freeradius dan sesuaikan path-nya.

Uji Ulang Patch di Lab
-- interim-update SESUDAH patch, 2 menit lalu, hanya untuk budi dan sari
SET @ev = NOW() - INTERVAL 2 MINUTE;
UPDATE radacct
SET acctupdatetime = (@old := acctupdatetime),
acctupdatetime = @ev,
acctstoptime = IF(acctstoptime IS NOT NULL AND acctstoptime < @ev, NULL, acctstoptime),
acctterminatecause = IF(acctstoptime IS NULL, '', acctterminatecause),
acctinterval = UNIX_TIMESTAMP(@ev) - UNIX_TIMESTAMP(@old)
WHERE acctuniqueid IN (MD5('sesi-budi'), MD5('sesi-sari'));
SELECT username, acctupdatetime, acctstoptime, acctterminatecause FROM radacct;
Kali ini budi dan sari kembali terbuka dengan acctstoptime NULL dan alasan kosong. Sementara itu, andi tetap tertutup karena tidak ada interim untuknya. Di produksi, kami juga menguji SQL ini di dalam transaksi yang di-rollback, termasuk kasus sesi dengan waktu stop di masa depan yang memang harus tetap tertutup.

Contoh Kasus 2: Sesi Hantu dari Router yang Mati
Masalah kebalikannya juga nyata. Pada awal Oktober 2026, dua router milik tenant lain berhenti terhubung ke server RADIUS dan tidak pernah kembali. Router yang mati mendadak tidak sempat mengirim paket Stop. Akibatnya, sekitar 105 sesi tetap terbuka di radacct berminggu-minggu, sehingga billing menghitung pelanggan itu sebagai online.

Screenshot di atas memperlihatkan pola yang jelas. NAS yang sehat punya last_upd beberapa detik lalu, sedangkan NAS yang mati berhenti melapor sejak 1 dan 2 Oktober. Statistik bawah juga menarik: dalam 30 hari ada 2.197 sesi berakhir dengan NAS-Reboot. Angka ini wajar karena router di beberapa tenant memang terjadwal restart berkala, dan setiap restart memicu Accounting-On.
Aturan 15 Menit untuk Sesi Hantu

Karena router melapor tiap lima menit, sesi yang tidak melapor tiga kali berturut-turut hampir pasti sudah mati. Oleh karena itu, billing menambahkan syarat kedua: COALESCE(acctupdatetime, acctstarttime) harus lebih baru dari 15 menit lalu. Fungsi COALESCE menangani sesi yang baru mulai dan belum pernah mengirim interim. Query berikut menghitung sesi hidup dan hantu per NAS di lab.
-- sesi hantu: router 192.0.2.3 mati tanpa sempat mengirim Stop
INSERT INTO radacct (acctsessionid, acctuniqueid, username, nasipaddress, framedipaddress,
acctstarttime, acctupdatetime, acctinterval, acctterminatecause)
VALUES ('81a00001', MD5('sesi-rina'), 'rina', '192.0.2.3', '10.1.0.21',
NOW() - INTERVAL 32 DAY, NOW() - INTERVAL 31 DAY, 300, '');
SELECT nasipaddress,
COUNT(*) AS sesi_terbuka,
SUM(COALESCE(acctupdatetime, acctstarttime) > NOW() - INTERVAL 15 MINUTE) AS hidup,
SUM(COALESCE(acctupdatetime, acctstarttime) <= NOW() - INTERVAL 15 MINUTE) AS hantu
FROM radacct
WHERE acctstoptime IS NULL
GROUP BY nasipaddress;
Hasil di lab: NAS 192.0.2.3 punya 1 sesi terbuka yang seluruhnya hantu, sedangkan NAS 192.0.2.8 punya 2 sesi hidup (budi dan sari). Kedua perbaikan ini saling melengkapi. Patch interim memulihkan sesi yang tertutup palsu, sedangkan aturan 15 menit membuang sesi yang terbuka palsu.
Halaman PPPoE Membaca radacct, Bukan RouterOS API
Dulu badge Online di halaman PPPoE berasal dari RouterOS API. Billing menghubungi setiap router dan menjalankan /ppp/active/print secara berurutan. Masalahnya, satu router offline membuat koneksi menggantung sampai timeout, sehingga semua pelanggan tenant itu tampil offline. Bahkan pelanggan di router yang sehat ikut terdampak. Sejak akhir Oktober 2026, halaman PPPoE dan dashboard membaca radacct dengan fungsi seperti ini.
<?php
// Sesi PPPoE yang benar-benar hidup menurut radacct (tanpa memanggil RouterOS API).
const ONLINE_FRESH_MINUTES = 15; // interim tiap 5 menit, toleransi 3x
function online_sessions(mysqli $db, array $usernames): array
{
$usernames = array_values(array_unique(array_filter($usernames, 'strlen')));
if (!$usernames) return [];
$in = implode(',', array_fill(0, count($usernames), '?'));
$sql = "SELECT username, framedipaddress, nasipaddress, acctstarttime, acctupdatetime,
acctinputoctets, acctoutputoctets
FROM radacct
WHERE acctstoptime IS NULL
AND COALESCE(acctupdatetime, acctstarttime) > NOW() - INTERVAL " . ONLINE_FRESH_MINUTES . " MINUTE
AND username IN ($in)
ORDER BY radacctid ASC";
$st = $db->prepare($sql);
$st->bind_param(str_repeat('s', count($usernames)), ...$usernames);
$st->execute();
$out = [];
foreach ($st->get_result() as $r) {
$out[$r['username']] = $r; // ORDER BY ASC: sesi terbaru menimpa yang lama
}
return $out;
}
$db = new mysqli('localhost', 'lab', getenv('LAB_PASS'), 'radius_lab');
$online = online_sessions($db, ['budi', 'sari', 'andi', 'rina']);
foreach (['budi', 'sari', 'andi', 'rina'] as $u) {
printf("%-5s %s\n", $u, isset($online[$u]) ? 'ONLINE ' . $online[$u]['framedipaddress'] : 'offline');
}
Penjelasan Fungsi online_sessions()
Fungsi ini menerima daftar username milik satu tenant. Daftar itu wajib, karena username RADIUS bersifat global lintas tenant. Kemudian prepared statement mencegah SQL injection dari username. Selanjutnya, ORDER BY radacctid ASC membuat sesi terbaru menimpa sesi lama bila satu username punya dua baris terbuka. Di lab, hasilnya budi dan sari ONLINE, sedangkan andi (tertutup) dan rina (hantu) offline.
| Aspek | RouterOS API per router | Query radacct |
|---|---|---|
| Waktu respons | Detik, bisa timeout bila router mati | 287 sesi dalam ±0,005 detik |
| Router offline | Semua badge jadi Offline | Hanya sesi router itu yang jadi hantu |
| Kesegaran data | Real time | Tertinggal maksimal 5 menit |
| Beban router | Satu koneksi API setiap kali halaman terbuka | Nol |
Sebelum beralih, kami mencocokkan kedua sumber. Hasilnya, 91 dari 93 sesi aktif di router tercatat di radacct dan tidak ada data basi. Selain itu, dashboard tenant yang dulu butuh sekitar 4,7 detik kini terbuka tanpa menunggu router sama sekali.
Kesalahan Umum
- Menganggap pelanggan benar-benar putus. Selalu bandingkan
acctupdatetimedenganacctstoptime. Jika update lebih baru, sesi itu masih hidup. - Menaruh baris acctterminatecause sebelum acctstoptime. Urutan SET menentukan hasil. Jika terbalik, alasan NAS-Reboot tidak terhapus walaupun sesi sudah terbuka.
- Mengaktifkan SIMULTANEOUS_ASSIGNMENT di MariaDB. Mode SQL ini membuat semua SET membaca nilai lama. Kami mengujinya di lab, dan hasilnya
acctstoptimeNULL tetapi alasan tetap NAS-Reboot. - Restart radiusd tanpa
radiusd -XC. Satu backslash yang hilang diqueries.confbisa membuat FreeRADIUS gagal start, sehingga semua pelanggan gagal login. - Memakai
acctstoptime IS NULLsaja. Tanpa batas 15 menit, router yang mati mendadak meninggalkan ratusan sesi online palsu. - Batas waktu lebih kecil dari interval interim. Jika interim 5 menit tetapi batasnya 5 menit, pelanggan akan berkedip offline. Gunakan minimal dua sampai tiga kali interval.
- Jam router tidak sinkron. Aktifkan NTP client di MikroTik agar timestamp accounting akurat sejak awal boot.
Kesimpulan
Status online di billing hanya seakurat isi tabel radacct FreeRADIUS. Pada kasus kami, query accounting-on menutup sesi yang masih hidup, lalu query interim-update bawaan tidak pernah membukanya lagi. Akibatnya, pelanggan online di MikroTik tetapi offline di billing selama berjam-jam. Untuk mengatasinya, kami menambahkan dua baris ke blok interim-update agar laporan yang datang setelah penutupan langsung membuka sesi kembali. Setelah patch terpasang pada 24 Oktober 2026, setiap pengecekan kami dengan query deteksi mengembalikan nol sesi terjebak.
Di sisi billing, aturan 15 menit membuang sesi hantu dari router yang mati, dan halaman PPPoE kini membaca radacct, bukan RouterOS API. Hasilnya, status pelanggan lebih akurat dan halaman tetap cepat walaupun ada router yang offline. Jika Anda mengelola FreeRADIUS sendiri, jalankan dulu query deteksi di atas. Jika hasilnya tidak kosong, patch ini layak Anda pasang.
Baca Juga
- Cara Akses Proxmox dari Internet Tanpa IP Publik (WireGuard + VPS)
- Keamanan Server Linux: Tutorial Hardening SSH, Firewall UFW, CrowdSec, dan Alarm Telegram
- Cara Membuat Dashboard Monitoring Server (NOC) dengan Uptime Kuma dan Beszel
Referensi: FreeRADIUS: queries.conf MySQL (v3.2.x), FreeRADIUS Wiki: rlm_sql, RFC 2866: RADIUS Accounting, MikroTik RouterOS: RADIUS, MariaDB: UPDATE.

