Quartz.NETQuartz.NET
Home
Features
Discussions
NuGet
GitHub
Home
Features
Discussions
NuGet
GitHub
  • 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
  • Next Release

    • Quartz 4.x
      • 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
      • Configuration Reference
      • JSON Configuration
      • Cron Expression Reference
      • Multi-Tenancy
      • Frequently Asked Questions
      • Best Practices
      • Before You Go Live
      • Operating a Cluster
      • Log Events
      • Tenancy Patterns
      • Database Schema
      • Database Schema Changes
      • Migration Guide
      • Troubleshooting
      • How To's
        • One-Off Job
        • Rescheduling Jobs
        • Retrying Failed Jobs
        • Multiple Triggers
        • Job Template
        • Running Quartz under Aspire
        • Quartz.NET with Wolverine
        • 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
          • Hosted Services Integration
          • Microsoft DI Integration
          • Multiple Schedulers with Microsoft DI
          • Observability
          • 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

For a scheduler that works in development and is about to carry production work. Nothing here is new — every line is a decision one of the other pages already explains, and the link goes to that page. The assembly is the point: these are the things that are cheap to get right on Friday and expensive to discover on Monday.

Skip what does not apply. A single-node in-memory scheduler owes you nothing on the schema lines; a worker with no web stack owes you nothing on the security ones.

Configuration

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

  2. InstanceId is unique per node, and stable across restarts if you use node affinity. The container case has its own answer — Naming a node in a container.

  3. StoreJobDataAsStrings is on unless something in your job data genuinely is not a string. It is the setting that 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 rather than from the default of 10 — Sizing a cluster. It bounds MaxBatchSize, and startup says so 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 one that is not a job's — 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 agree with your longest job. A shutdown budget shorter than the job is a job killed 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, and with a persistent store that is a schedule that grows — Persistent job stores.

Schema

  1. SchemaProvisioning is left at Validate, and whatever applies the rest of your schema applies Quartz's — creating tables needs a permission a production database is usually right not to grant — Creating the schema.
  2. The fresh-install script's drop switch is 0 if you run one against a database that already has data. It defaults to drop — Schema first, then nodes.
  3. Upgrading from 3.x: the 4.0 migration is applied, and the cron audit was run first. The migration is mandatory even for a database that took every optional 3.x one — Database Schema Migration — and a stored expression 4.x rejects fails the read of the trigger, not only its firing — Before you upgrade. The whole ordered sequence is Upgrading a running deployment.
  4. The listing and acquisition indexes are present if 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 of that count tagged error.type is the number of failures, so one instrument answers both — Metrics.
  2. There is an alert on a job you expect to see regularly. The health check does not assert that anything is firing, and a scheduler with an empty schedule is healthy by its definition — Health checks and probes.
  3. quartz.trigger.misfire and quartz.cluster.recovery.trigger are alerted on. Both are counters that should normally stay flat, which makes them cheap to watch — 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 your 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. An id is stable across releases where a message's wording is not — Log Events.

Secure

  1. MapQuartzHttpApi() says what it means about authorization, IncludeStackTraceInProblemDetails is off, and MaxPageSize is left set. A job scheduled through the API names its type as a string the request supplies — Production hardening.
  2. The dashboard is behind a policy, or 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, it is readable in the database, and it is 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 really run in, and across a daylight-saving transition if they cross one. This costs microseconds and needs no scheduler — 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 — which is a property of your job rather than 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, because what a restore means for work that was in flight when the backup was taken is not obvious — Backup and restore.

See also

  • Operating a Cluster — the day-two half of this list, in full
  • 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: 9/2/26, 11:27 PM
Contributors: Marko Lahma, Claude Fable 5.1
Prev
Best Practices
Next
Operating a Cluster