Quartz.NETQuartz.NET
Home
Features
Blog
Discussions
NuGet
GitHub
Home
Features
Blog
Discussions
NuGet
GitHub
  • Getting Started

    • Overview
    • Quartz 4 Quick Start
    • Tutorial
      • Using Quartz
      • Jobs And Triggers
      • More About Jobs & JobDetails
      • Job Data
      • More About Triggers
      • Querying Jobs and Triggers
      • Simple Triggers
      • Cron Triggers
      • RecurrenceTrigger
      • Time and TimeProvider
      • Trigger and Job Listeners
      • Scheduler Listeners
      • Job Execution Middleware
      • Job Stores
      • Configuration, Resource Usage and Building a Scheduler
      • Building a Scheduler Without a Host
      • Clustering
      • Execution Groups
      • Node Affinity (Preferred Node)
      • Testing
      • Compile-Time Checks
      • Declaring Jobs with Attributes
      • Delegate Jobs
    • Configuration Reference
    • JSON Configuration
    • Cron Expression Reference
    • Multi-Tenancy
    • Comparison
    • Frequently Asked Questions
    • Best Practices
    • Before You Go Live
    • Operating a Cluster
    • Log Events
    • Tenancy Patterns
    • Database Schema
    • Database Schema Changes
    • Migration Guide
    • Troubleshooting
    • API Documentation
  • How To's
    • One-Off Job
    • Rescheduling Jobs
    • Backfill
    • Retrying Failed Jobs
    • Pausing with a Reason
    • Job Continuations
    • Overlap Policy
    • Progress and Execution Logs
    • Job Outcomes
    • Multiple Triggers
    • Job Template
    • Running Quartz under Aspire
    • Quartz.NET with Wolverine
    • Coming from Hangfire
    • Coming from TickerQ
    • Embedding Quartz in a Library
    • Running under an External Leader Election
    • Publishing Trimmed and Native AOT
    • Extending Quartz: what is open, what is closed, and how to ask
    • A Job Store of Your Own
    • A Driver Delegate for a New Database
    • Persisting a Custom Trigger Type
    • A Lock Handler of Your Own
  • Packages

    • Quartz Core Additions

      • Jobs
      • Serialization (System.Text.Json)
      • JSON Serialization
      • Plugins
    • Integrations

      • Aspire Integration
      • ASP.NET Core Integration
      • HTTP API
      • HTTP Client
      • Dashboard
      • Dashboard Agent
      • Hosted Services Integration
      • Microsoft DI Integration
      • Multiple Schedulers with Microsoft DI
      • Observability
      • Redis Lock Handler
      • TimeZoneConverter Integration
      • Weasel Schema Management
    • 3rd Party Plugins for Quartz
  • Quartz 3.x

    • Getting Started

      • Quartz 3 Quick Start
      • Tutorial
        • Using Quartz
        • Library Overview
        • Jobs And Triggers
        • More About Jobs
        • More About Triggers
        • Execution Groups
        • Node Affinity (Preferred Node)
        • Simple Triggers
        • Cron Triggers
        • RecurrenceTrigger
        • Trigger and Job Listeners
        • Scheduler Listeners
        • Job Stores
        • Tuning the Scheduler
        • Configuration, Resource Usage and SchedulerFactory
        • Advanced (Enterprise) Features
      • Configuration Reference
      • JSON Configuration
      • Multi-Tenancy
      • Frequently Asked Questions
      • Best Practices
      • Tenancy Patterns
      • Troubleshooting
      • API Documentation
      • Database Schema
      • Database Schema Changes
      • Migration Guide
      • Miscellaneous Features
    • How To's

      • One-Off Job
      • Multiple Triggers
      • Job Template
      • Using the CronTrigger
      • Rescheduling Jobs
    • Packages

      • Quartz Core Additions

        • Dashboard
        • Jobs
        • Serialization (System.Text.Json)
        • Serialization (Newtonsoft Json.NET)
        • Plugins
      • Integrations

        • ASP.NET Core Integration
        • Hosted Services Integration
        • Microsoft DI Integration
        • Multiple Schedulers with Microsoft DI
        • OpenTelemetry Integration
        • OpenTracing Integration
        • Redis Lock Handler
        • TimeZoneConverter Integration
      • 3rd Party Plugins for Quartz
  • Old Releases

    • Quartz 2.x
      • Quartz 2 Quick Start
      • Tutorial
        • Lesson 1: Using Quartz
        • Lesson 2: Jobs And Triggers
        • Lesson 3: More About Jobs & JobDetails
        • Lesson 4: More About Triggers
        • Lesson 5: SimpleTrigger
        • Lesson 6: CronTrigger
        • Lesson 7: TriggerListeners and JobListeners
        • Lesson 8: SchedulerListeners
        • Lesson 9: JobStores
        • Lesson 10: Configuration, Resource Usage and SchedulerFactory
        • Lesson 11: Advanced (Enterprise) Features
        • Lesson 12: Miscellaneous Features of Quartz
        • CronTrigger Tutorial
      • Configuration Reference
      • Migration Guide
      • API Documentation
    • Quartz 1.x
      • Tutorial
        • Lesson 1: Using Quartz
        • Lesson 2: Jobs And Triggers
        • Lesson 3: More About Jobs & JobDetails
        • Lesson 4: More About Triggers
        • Lesson 5: SimpleTrigger
        • Lesson 6: CronTrigger
        • Lesson 7: TriggerListeners and JobListeners
        • Lesson 8: SchedulerListeners
        • Lesson 9: JobStores
        • Lesson 10: Configuration, Resource Usage and SchedulerFactory
        • Lesson 11: Advanced (Enterprise) Features
        • Lesson 12: Miscellaneous Features of Quartz
      • API Documentation
  • License

Before You Go Live

Each line is a decision another page explains; the link goes to that page. Skip what does not apply: a single-node in-memory scheduler can skip the schema lines, and a worker with no web stack the security ones.

Configuration

  1. InstanceName is set, and the same on every node of the cluster. It is the SCHED_NAME column of every row: two nodes with different names sharing a database are two schedulers, not one cluster — Clustering.

  2. InstanceId is unique per node, and stable across restarts if you use node affinity. A clustered node with no InstanceId generates one at every start: unique, not stable. For containers, see Naming a node in a container. If nodes serve different execution groups, every group in use has a live node that serves it, and the health check is on.

  3. StoreJobDataAsStrings is on, unless your job data holds something that is not a string. It keeps a stored map readable and free of class-versioning problems — Storing job data as strings.

  4. MaxConcurrency is a number you chose, derived from what the database can serve across every node, not left at the default of 10 — Sizing a cluster. It bounds MaxBatchSize, and startup warns when the two disagree:

    MaxBatchSize is 25, which is more than the thread pool's MaxConcurrency of 10. Triggers acquired
    beyond the number of threads available to run them are held by this node until the pool drains.
    
  5. The connection pool is at least MaxConcurrency plus three. The scheduling loop, the misfire handler and the cluster check-in each need a connection of their own — The connection pool is the thread pool plus three.

  6. CommandTimeout is set. The provider default is usually thirty seconds and applies to a statement that has already started — CommandTimeout.

  7. WaitForJobsToComplete and HostOptions.ShutdownTimeout fit your longest job. A shutdown budget shorter than the job kills it mid-flight on every deploy — Shutdown has a budget.

  8. Every job and trigger has a name you chose. A generated name is a new row on every start, so a persistent schedule keeps growing — Persistent job stores.

Schema

  1. SchemaProvisioning is left at Validate, and Quartz's schema is applied by whatever applies the rest of yours. Creating tables needs a permission production databases usually do not grant — Creating the schema.
  2. The fresh-install script's drop switch is 0 when you run one against a database that already has data. It defaults to drop — Schema first, then nodes.
  3. Upgrading from 3.x: the cron audit was run, then the 4.0 migration was applied. The migration is mandatory even for a database that took every optional 3.x one — Database Schema Migration. A stored expression 4.x rejects fails the read of the trigger, not only its firing — Before you upgrade. The full sequence is Upgrading a running deployment.
  4. The listing and acquisition indexes are present once the schema is large enough for a scan to show — Indexes, and the acquisition index in particular.

Monitor

  1. quartz.job.execution.duration is exported. Its count is the number of executions, and the part tagged error.type is the number of failures — Metrics.
  2. There is an alert on a job you expect to see regularly. The health check does not assert that anything is firing; a scheduler with an empty schedule is healthy — Health checks and probes.
  3. quartz.trigger.misfire and quartz.cluster.recovery.trigger are alerted on. Both counters normally stay flat — Metrics.
  4. Somebody watches the node listing — QueryClusterNodes(), GET /schedulers/{name}/nodes, or the dashboard's Cluster page — Reading the cluster.
  5. The name attributes are dropped in a view before they reach the backend, unless job and trigger names are a bounded set. A per-tenant trigger name is unbounded cardinality — Metrics.
  6. The event ids you alert on are written down. Ids are stable across releases; message wording is not — Log Events.
  7. Warning 3716 and quartz.jobstore.lock.wait.duration are alerted on. A node blocked on the job store lock produces no other signal — Log Events and Metrics.

Secure

  1. MapQuartzHttpApi() states its authorization, IncludeStackTraceInProblemDetails is off, and MaxPageSize is left set. A job scheduled through the API names its type in a string from the request — Production hardening.
  2. The dashboard is behind a policy, read-only, or both. Its pages start, stand by, shut down, pause, resume, delete and trigger — Production hardening.
  3. No secrets are in a JobDataMap. It is persisted, readable in the database, and on every listing the API and the dashboard serve — Keep job data small, string-safe and free of secrets.

Rehearse

  1. The schedules are asserted in the time zone they will run in, and across a daylight-saving transition if they cross one. This needs no scheduler and takes microseconds — Level 0: schedules, with no scheduler and Crossing a daylight-saving transition.
  2. One failover has been rehearsed. Kill a node mid-job, watch the recovery, and confirm the work did not run twice (a property of your job, not of the scheduler) — When a peer takes over and Assume the job will run more than once.
  3. One restore has been rehearsed on a copy, to see what a restore does to work that was in flight at backup time — Backup and restore.
  4. A node that vanishes while holding the lock has been survived. Cutting a node's network is a different test from killing it: only the first leaves the lock held — A Lock Held by a Connection That Is Gone.

See also

  • Operating a Cluster — the day-two half of this list
  • Best Practices — the decisions behind most of the lines above
  • Troubleshooting — for when one of them was missed
Help us by improving this page!
Last Updated: 10/18/26, 9:21 AM
Contributors: Marko Lahma, Claude Opus 5.5
Prev
Best Practices
Next
Operating a Cluster