The xmlrpc.server module provides a basic framework for XML-RPC servers written in Python. It receives HTTP POST requests containing XML, converts parameters into Python objects, executes a registered function, and serializes the result. It can support legacy integrations, local tests, and controlled internal systems that already depend on the protocol.
The official documentation warns that the module is not secure against maliciously constructed XML data. In addition, SimpleXMLRPCServer does not provide a complete authentication system, rate limiting, abuse protection, or an internet-ready deployment architecture. Treat it as an internal component behind additional controls.
A minimal server
from xmlrpc.server import SimpleXMLRPCServer
with SimpleXMLRPCServer(
("127.0.0.1", 8000),
allow_none=False,
logRequests=False,
use_builtin_types=True,
) as server:
@server.register_function(name="add")
def add(a: int, b: int) -> int:
return a + b
server.serve_forever()
Binding to 127.0.0.1 prevents direct connections from other machines. For an internal production service, place an authenticated reverse proxy in front and keep the process on a restricted network.
Restricting the RPC path
The default request handler accepts / and /RPC2. A subclass can allow only one path.
from xmlrpc.server import (
SimpleXMLRPCRequestHandler,
SimpleXMLRPCServer,
)
class Handler(SimpleXMLRPCRequestHandler):
rpc_paths = ("/RPC2",)
server = SimpleXMLRPCServer(
("127.0.0.1", 8000),
requestHandler=Handler,
)
Path restriction removes accidental endpoints, but it is not authentication. A client that can reach the path can still submit method calls.
Explicit function registration
Prefer register_function() with clear public names. It creates an allowlist of exposed operations.
def status() -> dict[str, object]:
return {"ok": True, "version": "1"}
server.register_function(status, "system.status")
Validate types, ranges, string lengths, and collection counts inside each function. Python type annotations do not validate incoming RPC parameters automatically.
Do not expose dangerous functions
Avoid registering functions that execute commands, open arbitrary paths, evaluate code, import modules, or accept raw SQL. XML-RPC is only the transport; all risks of the underlying operation remain.
from pathlib import Path
ROOT = Path("/srv/reports").resolve()
def read_report(name: str) -> str:
if not name.endswith(".txt") or "/" in name or "\\" in name:
raise ValueError("invalid name")
path = (ROOT / name).resolve()
if ROOT not in path.parents:
raise ValueError("path outside root")
return path.read_text(encoding="utf-8")
Even on an internal network, treat every parameter as untrusted.
register_instance and _dispatch
register_instance() can expose methods from an object. A safer design implements _dispatch() and maps allowed names explicitly.
class Service:
def _dispatch(self, method, params):
allowed = {
"system.status": self.status,
"calculate.add": self.add,
}
function = allowed.get(method)
if function is None:
raise ValueError("method not allowed")
return function(*params)
def status(self):
return {"ok": True}
def add(self, a, b):
return int(a) + int(b)
server.register_instance(Service())
The allowlist prevents internal attributes from becoming remotely discoverable.
Never enable allow_dotted_names on an open network
The Python documentation explicitly warns that allow_dotted_names=True can expose module globals and may allow arbitrary code execution. Keep the default False. If a legacy client needs hierarchical method names, register those names explicitly or implement a controlled _dispatch().
Introspection
register_introspection_functions() exposes system.listMethods, system.methodHelp, and system.methodSignature.
server.register_introspection_functions()
This helps during development, but it reveals method surface. Enable it in production only when necessary and protected. Public documentation should never list administrative operations unintentionally.
Multicall
register_multicall_functions() enables system.multicall, allowing several operations in one request.
server.register_multicall_functions()
Batching reduces network round trips but increases work per request. Limit the number of calls, total cost, and response size. Do not batch destructive operations without defined atomicity and partial-failure behavior.
Types and allow_none
XML-RPC supports a limited set of types. allow_none=True enables an extension for None that some clients do not implement. use_builtin_types=True simplifies date and binary handling.
Document each method’s accepted types, limits, date timezone, maximum binary size, and meaning of missing values. For large files, use a dedicated upload service.
Faults and error messages
Exceptions raised by registered functions become XML-RPC faults. Do not return stack traces, filesystem paths, SQL, or credentials. Convert known domain failures into stable codes.
from xmlrpc.client import Fault
def get_user(user_id: int):
if user_id <= 0:
raise Fault(400, "invalid identifier")
user = repository.get(user_id)
if user is None:
raise Fault(404, "user not found")
return user
The message still needs escaping when a client displays it in HTML.
Authentication
SimpleXMLRPCServer has no complete modern authentication facility. A safer architecture places it behind Nginx, Apache, an API gateway, or a service mesh that validates mTLS, Basic Authentication, tokens, or workload identity.
A custom request handler can inspect headers, but ensure credentials never reach logs. Centralize the policy and test 401 and 403 responses.
TLS
The simple server does not offer an ergonomic production HTTPS setup. Terminate TLS at an updated reverse proxy that manages certificates, protocol versions, body limits, timeouts, and access logs.
Wrapping the socket manually makes your application responsible for handshake, shutdown, and security updates. The guide to Python ssl explains the risks.
Body limits and malicious XML
The documentation’s warning must be taken seriously. XML can be crafted to consume memory or CPU. Enforce a Content-Length limit at the proxy, reject unsupported transfer behavior, configure timeouts, and cap concurrent connections.
Do not expose the server to anonymous clients. Network segmentation and authentication reduce risk but do not replace resource limits.
Concurrency
SimpleXMLRPCServer is based on socketserver.TCPServer and handles requests synchronously. One slow method blocks later requests.
from socketserver import ThreadingMixIn
from xmlrpc.server import SimpleXMLRPCServer
class ThreadedXMLRPCServer(ThreadingMixIn, SimpleXMLRPCServer):
daemon_threads = True
Threads increase concurrency but also introduce races, resource consumption, and overload. Protect shared state, limit connections, and avoid long-running operations. The guide to Python socketserver covers mixins and shutdown.
Timeouts and long jobs
Do not run large reports, video processing, or unbounded tasks in the request thread. Queue the work, return a job identifier, and expose a status method. Configure deadlines at both proxy and client.
Graceful shutdown
Use the server as a context manager and call shutdown() from a different thread while serve_forever() is active. Then close application resources.
try:
server.serve_forever()
except KeyboardInterrupt:
pass
finally:
server.server_close()
In managed environments, handle SIGTERM and stop accepting new work before process termination.
Logging
logRequests=True provides simple request logs. Production systems should use structured events containing logical method name, duration, summarized result, and correlation identifier. Do not log complete XML bodies, tokens, or personal data.
Generated documentation
DocXMLRPCServer can serve an HTML documentation page for GET requests. It is convenient in a lab, but it may reveal method names and internal documentation. Do not expose it without authentication and review.
The matching client
Consume the service with Python xmlrpc.client. The client should use HTTPS, a timeout transport, a method allowlist, and separate handling for Fault and ProtocolError.
Recommended tests
Test valid and missing methods, out-of-range parameters, oversized bodies, malformed XML, missing authentication, wrong paths, concurrent calls, excessive multicall, graceful shutdown, and the absence of secrets in faults and logs.
Run local tests and never use a public third-party server to validate destructive code.
When to choose another technology
For a new API, HTTP frameworks with JSON, schemas, and security middleware are generally more appropriate. XML-RPC is mainly useful for compatibility with existing systems or simple internal tools on a controlled network.
Conclusion
xmlrpc.server can create an RPC endpoint quickly, but speed does not remove security responsibilities. Register functions explicitly, keep allow_dotted_names disabled, restrict paths and payloads, authenticate at a proxy, protect TLS, control concurrency, and treat all XML as untrusted.
Read the official xmlrpc.server documentation and Python’s XML security guidance. For public internet services or new projects, consider an API platform with more complete controls.







