Manage SQL Server Agent jobs across every server you own — without opening a single inbound port.
Quick start · Capabilities · Security · FAQ · Roadmap
You have 50 SQL Servers across half a dozen firewalled network segments. To see why a job failed last night you RDP into a box and open SSMS. To check whether anyone still runs that decommissioned feed, you open 50 boxes.
Everything that claims to fix this needs the management plane to reach the instances — MSX/TSX, Central Management Servers, most commercial tools. Across segmented corporate networks, that is the one thing you cannot have.
A small worker runs next to each SQL Server and dials outbound to a central control plane. It mirrors the local Agent — jobs, steps, schedules, operators, alerts, history and logs — into a dashboard that gives you an SSMS-shaped view of every server at once. Authorised changes sync back down.
Segment A Segment B Segment C
┌──────────┐ ┌──────────┐ ┌──────────┐
│ SQLPROD1 │ │ SQLPROD2 │ │ SQLPROD3 │
│ ┌──────┐ │ │ ┌──────┐ │ │ ┌──────┐ │
│ │Worker│ │ │ │Worker│ │ │ │Worker│ │
│ └──┬───┘ │ │ └──┬───┘ │ │ └──┬───┘ │
└────┼─────┘ └────┼─────┘ └────┼─────┘
│ outbound only, mTLS or API key over TLS │
└──────────────────┬─────┴──────────────────────────┘
▼
┌───────────────┐ ┌──────────┐
│ Control plane │───────▶│ Postgres │
└───────┬───────┘ └──────────┘
│ HTTPS
┌────▼────┐
│Dashboard│
└─────────┘
No inbound firewall rules. No listening socket on any SQL host.
The worker never executes a job. It reads msdb and, when permitted, calls the
documented Agent stored procedures — the same ones SSMS calls. Stop the worker
and every job keeps running on schedule. Uninstall it and nothing changes except
that you lose the central view.
Zero migration, zero lock-in.
See the estate. Every instance, its jobs, run history with step-level detail and messages, and live activity — in one grid.
Catch drift. Someone edits a job in SSMS at 2am? It appears as a new version, flagged, with a diff. Drift is expected and surfaced, never silently overwritten.
Search across every server. "Which servers still reference LEGACYFIN01 in
a job step?" — full-text across every step body on every instance. SSMS cannot
answer this at any price.
Change things safely. Enable, disable, start, stop, and edit jobs with a Monaco T-SQL editor. Every change is signed, capability-checked, conflict-checked and audited, with a second-approver rule on job edits.
Prove what happened. Every sign-in, change and worker session is recorded and exportable to any OpenTelemetry backend.
Least privilege throughout. Read-only by default. Write capability is opt-in per worker and per capability, and the worker enforces its own ceiling from local config — so a compromised control plane still cannot make a read-only worker write.
Full walkthrough: docs/quick-start.md — about 30 minutes, most of it waiting for containers. Where to run it, what it costs, and the two constraints that decide both: docs/deployment.md.
No checkout needed — it ships as an image. On any host with Docker:
mkdir -p rsagent && cd rsagent
BASE=https://raw.githubusercontent.com/semics-tech/remote-sql-agent/main/deploy
curl -fsSLO $BASE/docker-compose.yml
curl -fsSLO $BASE/Caddyfile
curl -fsSL $BASE/.env.example -o .env # then edit it
docker compose --profile tls up -d # with automatic HTTPSOr hand deploy/cloud-init.yaml to a fresh VM as
user-data and it does all of the above on first boot.
The image is published to both registries, same digest:
docker pull techsemics/remote-sql-agent:latest
docker pull ghcr.io/semics-tech/remote-sql-agent/control-plane:latestPut a TLS certificate for the hub in ./tls. The control plane refuses to start
without one: mTLS needs it to exist at all, and in token mode it is the only
thing keeping a worker's API key off the wire in clear. From a checkout,
pnpm dev:cert <hostname> writes a self-signed one for a lab.
On first boot it creates an administrator and prints a generated password once — copy it from the log.
Generate a single-use enrolment token in the dashboard (Administration → Workers), then on the SQL Server host:
.\install.ps1 -ControlPlane rsagent.corp.example.com:8443 `
-EnrolmentToken rsen_xxxxxxxxxxxx `
-CaCertPath C:\certs\corp-ca.pemWorkers authenticate with an mTLS client certificate by default. There is no
certificate authority to set up — the control plane runs its own — and no
rotation to schedule: the worker renews its own certificate at half its
lifetime. On Azure or Arc-enabled hosts, -AuthMode entra stores no credential
on the host at all. Pick the mode when you mint the token; the dashboard's
install command carries the matching flag. See
docs/authentication.md.
SQL Server on Linux, or a host with nothing installed on it — download the executable for the platform from Releases. One file, no Node, no dependencies:
curl -fsSLO https://github.com/semics-tech/remote-sql-agent/releases/latest/download/rsagent-worker-linux-x64
chmod +x rsagent-worker-linux-x64
./rsagent-worker-linux-x64 enrol --token rsen_xxxxxxxxxxxx /etc/rsagent/worker.yaml
./rsagent-worker-linux-x64 /etc/rsagent/worker.yamlOr, on a host that already has Node 24:
npm install -g @remote-sql-agent/worker
rsagent enrol --token rsen_xxxxxxxxxxxx /etc/rsagent/worker.yaml
rsagent /etc/rsagent/worker.yamlEvery release asset is listed in SHA256SUMS and carries a build attestation,
so a download can be traced back to the workflow run that produced it:
gh attestation verify rsagent-worker-linux-x64 --repo semics-tech/remote-sql-agentRead-only mirroring needs one role and a set of table grants:
USE [msdb];
CREATE USER [CORP\SQLAGENT-SVC] FOR LOGIN [CORP\SQLAGENT-SVC];
ALTER ROLE [SQLAgentReaderRole] ADD MEMBER [CORP\SQLAGENT-SVC];sqlcmd -S localhost -E -i deploy/sql/worker-permissions.sqlThe role grants EXECUTE on the sp_help_* procedures, not SELECT on the tables
under them — and the worker reads those directly. Skip the second step and the
login browses jobs happily in SSMS, then fails in the worker with a permission
error on sysjobhistory.
Add SQLAgentOperatorRole only when you want the worker to make changes; that
one really is just the role. Never grant sysadmin.
A worker's effective capability is the intersection of two independently controlled sets:
server-side grant ∩ worker-side ceiling = what it will do
(dashboard) (worker.yaml)
The worker computes this itself, from its own config, on every session. A site
that pins maxCapability: readOnly has a worker that cannot be made to write
even if the control plane is fully compromised.
| Tier | Adds | Typical use |
|---|---|---|
readOnly |
observe | Default. Visibility, history, drift, search |
operate |
toggle, start/stop | On-call restarts a failed job without an RDP session |
schedule |
schedule edits | Schedules managed centrally |
full |
job and operator edits | Jobs authored from the dashboard |
job.write is effectively remote code execution on a database server — a job
step runs T-SQL, PowerShell or CmdExec. Choose deliberately;
docs/capabilities.md walks through it.
Microsoft Entra ID (app-role mapping), local accounts, or both. Keeping both leaves a break-glass administrator when the IdP is unreachable.
| Entra app role | Dashboard role | Can |
|---|---|---|
rsagent.viewer |
Viewer | Read everything |
rsagent.operator |
Operator | + enable/disable, start/stop |
rsagent.editor |
Editor | + edit schedules and jobs (subject to approval) |
rsagent.admin |
Admin | + workers, users, approvals, audit |
Setup: docs/authentication.md.
| quick-start.md | Nothing to a working estate view |
| capabilities.md | What a worker may do, and how to choose |
| notifications.md | Alerting to email, Slack, Teams or a webhook |
| authentication.md | Entra sign-in, worker auth, audit export |
| security.md | What is enforced, and a deployment checklist |
| threat-model.md | Scenarios, mitigations, residual risk |
| faq.md | Including "does this replace SQL Agent?" (no) |
| migration.md | Roadmap, known gaps, upgrade path |
| releasing.md | Cutting a release to npm, Docker Hub and GitHub |
| remote-sql-agent-architecture.md | The original design spec |
All six milestones of the architecture spec are implemented. 204 tests,
including an integration suite that drives a real worker against a real SQL
Server: starting jobs and waiting for their step-level history, editing a job the
way SSMS would and asserting drift attribution, and proving round-trip
fidelity — a definition sent from the dashboard is byte-for-byte identical when
read back from msdb.
Version 0.x: breaking changes may land in minor versions until 1.0.
Known gaps — no control-plane HA, manual certificate rotation, no MSI — are tracked with impact and effort in docs/migration.md.
Requires Node.js 24+, pnpm 10+ and Docker.
pnpm install
pnpm dev:up # Postgres + SQL Server 2022 with Agent enabled
pnpm dev:seed # ~10 varied fixture jobsThe SQL Server image is amd64 and runs under emulation on Apple Silicon. Allow 60–90s for it to become healthy, and give Docker at least 6 GB. Below that it is the container the kernel picks first:
docker ps -ashowsExited (137), and everything downstream looks like a connection bug instead.
Three long-running processes, one terminal each. All three are needed: the control plane serves the API, the worker is what actually talks to SQL Server, and without it the dashboard is an empty estate.
# 1. Control plane — API on :8080, worker hub on :8443
pnpm dev:server
# 2. Dashboard — http://localhost:5173, proxies /api to :8080
pnpm dev:dashboard
# 3. Worker — listens on nothing; dials out to the hub
pnpm dev:workerThe dashboard asks for credentials in development too. There is deliberately no local bypass: an authentication switch that can be turned off is one that eventually ships turned off, and every RBAC path in the product hangs off having a real signed-in user with a real role.
pnpm dev:server instead fixes the bootstrap password to something you already
know, by setting RSAGENT_BOOTSTRAP_ADMIN_PASSWORD:
username: admin
password: rsagent-dev
That only applies on first boot, when the database has no users yet. If you have an older dev database — or you changed the password and forgot it:
pnpm dev:reset-adminIt refuses to touch anything but a database on localhost.
In a real deployment neither exists: the control plane generates a password on first boot and prints it once.
The worker needs enrolling before step 3 works. Sign in, then Estate → Add a worker for a token:
pnpm dev:enrol --token rsen_xxxxxxxxxxxxThat writes packages/worker/run/worker.key (the credential) and
credential.key (the key SQL credentials are encrypted to). Delete either and
the worker cannot reconnect — rotate a new one from Administration → Workers.
Two gates, and both must allow it — see capabilities.md:
- Administration → Workers → Manage — tick the capabilities to grant.
deploy/worker.dev.yaml— raisemaxCapabilityfromreadOnly.
maxCapability is read once at startup, so restart the worker afterwards;
reconnecting re-sends the old value. Confirm it took by looking for
capabilities in the worker's Worker ready log line, or the "Can actually do"
column in Administration.
Two worker processes sharing one credential supersede each other in a loop — each connect kicks the other off. If capability changes appear to be ignored, or the estate flickers, check for a stray worker before anything else.
pnpm test # unit + integration
pnpm test:unit # no containers needed beyond Postgres
pnpm lint
pnpm typecheck
pnpm proto:check # fails if generated protobuf has driftedEdit a job the way a DBA would, directly in SQL:
docker exec rsagent-dev-sqlserver-1 /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U sa -P 'RsAgent_Dev_Pass123' -C -Q "
EXEC msdb.dbo.sp_update_jobstep
@job_name = N'RSAgent Fixture - Heartbeat Log', @step_id = 1,
@command = N'EXEC dbo.usp_LogMaintenance @Source = N''Heartbeat'', @Message = N''Edited in SSMS'';';"Within the poll interval the job shows a drift badge, and its Versions tab
has a new on-premise edit version with a diff of the changed step body.
packages/protocol .proto contracts, JobDefinition.v1 schema, canonical
hashing, schedule codec, capability model [published]
packages/worker Node daemon: msdb reader/writer, outbox, gRPC [published]
packages/server Control plane: gRPC hub, Postgres, REST API [container]
packages/dashboard React SPA [built into container]
deploy/ Dockerfile, Compose, installers, dev stack
docs/ everything above
The .proto files are the single source of truth for the wire contract.
Generated output is checked in so no contributor needs a protoc toolchain, and
CI fails if it drifts.
Contributions welcome — see CONTRIBUTING.md. Security issues: SECURITY.md, please do not open a public issue.
Apache 2.0. Fully open source, no open-core split.