Many new osu! private servers using bancho.py are being listed on https://osu-server-list.com without proper security measures. This guide provides detailed steps to protect your server from common attacks.
- Server Exposure Risks
- Firewall Configuration
- Rate Limiting Implementation
- Command Security
- Additional Protection Measures
- SSL Certificate Scanning: Services like Censys/Shodan scan port 443/80 and extract domain info from SSL certificates
- Direct IP Exposure: Unprotected bancho.py servers expose ports (10000/tcp) directly
- DNS Vulnerabilities: Unprotected DNS (port 53/udp) can be DDoSed and get blackhole
- Inadequate firewall rules
- Missing rate limiting
- Dangerous production commands left enabled
- Exposed database ports
- No DDoS protection
# Always allow SSH first to prevent lockout
sudo ufw allow 22/tcp
# Block known malicious IP ranges and scanners
sudo ufw deny from 162.142.125.0/24
sudo ufw deny from 167.94.138.0/24
sudo ufw deny from 198.20.69.72
sudo ufw deny from 198.20.69.96
sudo ufw deny from 93.120.27.62
sudo ufw deny from 66.240.236.119
sudo ufw deny from 71.6.135.131
sudo ufw deny from 82.221.105.6
sudo ufw deny from 82.221.105.7
sudo ufw deny from 188.138.9.50
sudo ufw deny from 85.25.103.50
sudo ufw deny from 198.20.69.72/29
sudo ufw deny from 198.20.69.96/29
sudo ufw deny from 198.20.70.104/29
sudo ufw deny from 198.20.99.128/29
sudo ufw deny from 198.20.87.96/29
sudo ufw deny from 66.240.192.138
sudo ufw deny from 71.6.167.142
sudo ufw deny from 71.6.165.200
sudo ufw deny from 85.25.43.94
sudo ufw deny from 71.6.146.185
sudo ufw deny from 71.6.146.186
sudo ufw deny from 71.6.158.166
sudo ufw deny from 66.240.219.146
sudo ufw deny from 209.126.110.38
sudo ufw deny from 104.236.198.48
sudo ufw deny from 104.131.0.69
sudo ufw deny from 162.159.244.38
sudo ufw deny from 159.203.176.62
sudo ufw deny from 188.138.1.119
sudo ufw deny from 80.82.77.33
sudo ufw deny from 80.82.77.139
sudo ufw deny from 71.6.146.130
sudo ufw deny from 66.240.205.34
sudo ufw deny from 216.117.2.180
sudo ufw deny from 93.174.95.106
sudo ufw deny from 89.248.172.16
sudo ufw deny from 185.163.109.66
sudo ufw deny from 89.248.167.131
sudo ufw deny from 94.102.49.190
sudo ufw deny from 94.102.49.193
sudo ufw deny from 185.181.102.18
sudo ufw deny from 167.94.145.0/24
sudo ufw deny from 167.94.146.0/24
sudo ufw deny from 167.248.133.0/24
sudo ufw deny from 199.45.154.0/24
sudo ufw deny from 199.45.155.0/24
sudo ufw deny from 206.168.34.0/24
sudo ufw deny from 198.20.69.72 to 198.20.69.79
sudo ufw deny from 198.20.69.96 to 198.20.69.103
sudo ufw deny from 198.20.70.111 to 198.20.70.119
sudo ufw deny from 198.20.99.128 to 198.20.99.135
sudo ufw deny from 198.20.87.96 to 198.20.87.103
sudo ufw deny from 176.124.204.0/24
sudo ufw deny from ::/0 to 2602:80d:1000:b0cc:e::/80
sudo ufw deny from ::/0 to 2620:96:e000:b0cc:e::/80
sudo ufw deny from ::/0 to 2602:80d:1003::/112
sudo ufw deny from ::/0 to 2602:80d:1004::/112
# Essential port configuration
sudo ufw allow 443/tcp # HTTPS
sudo ufw deny 10000/tcp # Bancho port
# Any port do you want to allow or deny
# Example: sudo ufw allow 25565/tcp (Allow MC TCP)
# Example 2: sudo ufw deny 19132/udp (Deny MC UDP)
# Enable firewall
sudo ufw enable
sudo ufw reload
sudo systemctl enable ufw
- MySQL/Redis should only listen on 127.0.0.1 (If you are deploy by using docker, nothing need to changes!)
- For development access (Access MySQL/Redis/etc), use ZeroTier or Tailscale instead of exposing ports
- Server IPs should disappear from scanners like Censys within 48 hours after these changes
- Navigate to: Security → WAF → Rate limiting rules
- Create new rule with these recommended settings:
Recommended Settings:
- Requests: 150-250 per 10 seconds (lower may cause issues, higher reduces protection) (Up to your frontend and etc)
Location: app/commands.py
# Change from UNRESTRICTED to ADMIN/DEVELOPER
@command(Privileges.ADMIN)
async def server(ctx: Context) -> str | None:
[existing implementation]
# Add a global cache for the !server command
_server_cache = {"data": None, "timestamp": 0}
@command(Privileges.UNRESTRICTED)
async def server(ctx: Context) -> str | None:
"""Retrieve performance data about the server."""
global _server_cache
# Define cache duration (e.g., 10 seconds)
CACHE_DURATION = 10
# Check if the cache is still valid
current_time = time.time()
if _server_cache["data"] and current_time - _server_cache["timestamp"] < CACHE_DURATION:
return _server_cache["data"]
# If cache is expired or empty, recalculate the server info
build_str = f"bancho.py v{app.settings.VERSION} ({app.settings.DOMAIN})"
# get info about this process
proc = psutil.Process(os.getpid())
uptime = int(time.time() - proc.create_time())
# get info about our cpu
cpu_info = cpuinfo.get_cpu_info()
# list of all cpus installed with thread count
thread_count = cpu_info["count"]
cpu_name = cpu_info["brand_raw"]
cpu_info_str = f"{thread_count}x {cpu_name}"
# get system-wide ram usage
sys_ram = psutil.virtual_memory()
# output ram usage as `{bancho_used}MB / {sys_used}MB / {sys_total}MB`
bancho_ram = proc.memory_info()[0]
ram_values = (bancho_ram, sys_ram.used, sys_ram.total)
ram_info = " / ".join([f"{v // 1024 ** 2}MB" for v in ram_values])
# current state of settings
mirror_search_url = urlparse(app.settings.MIRROR_SEARCH_ENDPOINT).netloc
mirror_download_url = urlparse(app.settings.MIRROR_DOWNLOAD_ENDPOINT).netloc
using_osuapi = bool(app.settings.OSU_API_KEY)
advanced_mode = app.settings.DEVELOPER_MODE
auto_logging = app.settings.AUTOMATICALLY_REPORT_PROBLEMS
# package versioning info
# divide up pkg versions, 3 displayed per line, e.g.
# aiohttp v3.6.3 | aiomysql v0.0.21 | bcrypt v3.2.0
# cmyui v1.7.3 | datadog v0.40.1 | geoip2 v4.1.0
# maniera v1.0.0 | mysql-connector-python v8.0.23 | orjson v3.5.1
# psutil v5.8.0 | py3rijndael v0.3.3 | uvloop v0.15.2
requirements = []
for dist in importlib.metadata.distributions():
requirements.append(f"{dist.name} v{dist.version}")
requirements.sort(key=lambda x: x.casefold())
requirements_info = "\n".join(
" | ".join(section)
for section in (requirements[i : i + 3] for i in range(0, len(requirements), 3))
)
result = "\n".join(
(
f"{build_str} | uptime: {timedelta(seconds=uptime)}",
f"cpu: {cpu_info_str}",
f"ram: {ram_info}",
f"search mirror: {mirror_search_url} | download mirror: {mirror_download_url}",
f"osu!api connection: {using_osuapi}",
f"advanced mode: {advanced_mode} | auto logging: {auto_logging}",
"",
"requirements",
requirements_info,
),
)
# Update the cache
_server_cache["data"] = result
_server_cache["timestamp"] = current_time
return result
@command(Privileges.DEVELOPER) # Changed from UNRESTRICTED
async def apikey(ctx: Context) -> str | None:
[existing implementation]
-
Cloudflare Paid Plans (If you are rich):
- Business/Enterprise plans offer better DDoS protection
- Enable "Under Attack" mode during attacks
-
Server Providers (Better plan if you can earn money from this project but how?):
- OVH: Built-in DDoS protection (Only this thing in my brain)
-
Set up alerts for:
- High CPU usage
- Unusual traffic patterns
-
Recommended tools:
- fail2ban for SSH protection
- Datadog for performance monitoring
- Cloudflare Analytics for traffic insights
Credits:
- @blueskychan-dev
- Notmycode Foundation (@notmycode-labs)
- osu!somtum (@osu-somtum)