Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Query Prometheus by writing a PromQL expression in the web console or sending that expression to the HTTP API. Begin with a metric selector, narrow it with label matchers, add a range selector when a function needs a history of samples, and then aggregate or compare the resulting series. Use the Table view to verify an instant result before putting the expression on a graph, dashboard, alert, or script.

PromQL is Prometheus’s functional query language for selecting and aggregating time-series data in real time. The same expression can be evaluated once at a timestamp (an instant query) or repeatedly at fixed intervals across a start and end time (a range query). Understanding that distinction, along with metric types, labels, and result cardinality, prevents most confusing Prometheus results.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What PromQL queries actually return

A Prometheus time series is identified by a metric name plus its set of labels. Each sample has a timestamp and a numeric value. PromQL evaluates an expression against those series and returns one of four documented data types:

Type What it contains Typical use
Instant vector One sample for each matching series at one evaluation timestamp Current values, rates at “now,” and dashboard panels
Range vector Samples for each matching series over a lookback interval Input to functions such as rate() and increase()
Scalar One numeric value Constants, arithmetic, and percentages
String A string value; it is currently unused in the documented expression model Not normally used in PromQL queries

An instant vector is not the same thing as a range vector containing one sample. The distinction matters because functions and operators require particular input types. A range query’s root expression must produce a scalar or instant vector; the range query engine evaluates that expression once per step and returns the sequence of results.

Prometheus’s documentation describes PromQL as a language that lets users select and aggregate time-series data in real time. That makes a query more than a database lookup: functions can calculate rates, arithmetic can derive percentages, and aggregators can reduce thousands of series to the dimensions that matter.

Run your first query in the Prometheus web UI

The expression browser is the fastest place to learn what your installation actually exposes. Metric names and labels depend on the exporters and application instrumentation you have deployed, so start by inspecting real data rather than copying a selector whose labels do not exist in your environment.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Open the Prometheus web interface and select the query or expression page.
  2. Enter up and execute it. The result shows whether each discovered target is currently reporting. If your deployment uses a different set of scrape targets, the series and labels will differ.
  3. Stay in the Table view while exploring. It performs an instant query and shows the labels and value for each returned series.
  4. Once the selector is correct, switch to the Graph view to evaluate it repeatedly over a time interval. Graph view is intended for range queries.
  5. Read the label set on every returned series before adding a function or aggregation. Those labels determine whether the result is one series, one series per service, or thousands of separate series.

The tabular view is also a safety check. A bare selector for a high-cardinality metric can expand to a very large result set, making a query expensive and difficult to interpret. Narrow it before building a dashboard panel or alert.

Build a metric selector

A metric name by itself selects every series with that name. For example:

http_requests_total

To select only series matching labels, put comma-separated matchers in braces:

http_requests_total{job="prometheus",group="canary"}

The four matcher operators are:

Operator Meaning Example
= Label equals an exact value {job="prometheus"}
!= Label does not equal a value {job!="prometheus"}
=~ Label matches a regular expression {job=~"api|worker"}
!~ Label does not match a regular expression {job!~"test|canary"}

PromQL regular-expression matchers use RE2 syntax and are fully anchored. In practice, job=~"api" matches the complete label value api, not every value containing the letters “api.” To match a value containing that text, write a pattern such as .*api.*. Check your installation’s labels in the table result before relying on a pattern.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Matchers can be combined to make a selector precise:

http_requests_total{job="api",instance=~"web-[0-9]+:8080",status!="200"}

A selector with no matching series is not necessarily a syntax error. It can mean the metric name, label name, or label value is different from what your instrumentation emits, or that no sample is available at the evaluation time. Test the metric name alone, then add one matcher at a time.

Use range selectors for recent history

Appending a duration in square brackets turns an instant-vector selector into a range vector. This example selects the samples from the last five minutes for every matching series:

http_requests_total{job="prometheus"}[5m]

Range intervals are left-open and right-closed: the start boundary is excluded and the end boundary is included. Common duration units include seconds (s), minutes (m), hours (h), days (d), weeks (w), and years (y). Choose an interval long enough to contain several scrapes; a window that is shorter than or barely equal to the scrape interval can produce unstable or empty results.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A range selector by itself is usually not the value you want to display. Counter-oriented functions consume it to calculate a useful instant vector.

Calculate a per-second counter rate

Use rate() for the average per-second increase of a counter over a period:

rate(http_requests_total{job="prometheus"}[5m])

The metric name in this example is a counter: it is expected to increase over time, apart from resets when a process restarts. Apply rate() or increase() to counters, not to arbitrary gauges. The function’s five-minute window is part of the query’s meaning: a longer window smooths short spikes, while a shorter window responds faster but can be noisier.

Calculate the number added during a window

increase() expresses the counter growth over the selected interval:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
increase(http_requests_total{job="prometheus"}[1h])

Because counters can reset, these functions account for the reset behavior rather than treating a restart as a genuine negative workload change. Confirm the metric’s type and instrumentation before interpreting the result.

Aggregate without losing the dimension you need

Selectors often return one series per instance, status, route, or other label combination. Aggregation operators combine those series. The most useful form is usually sum by (...), which retains only the labels named in the parentheses.

Current request count by service

sum by (job) (http_requests_total)

This produces one series for each job, summing away other labels such as instance or status. If you need a fleet-wide total, omit the grouping labels:

sum(http_requests_total)

That total is meaningful only if summing the underlying series is appropriate. For counters, a current raw total is often less useful than a rate:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
sum by (job) (rate(http_requests_total[5m]))

Here the five-minute rate is calculated per series first, then the rates are summed by job. Keeping the grouping label preserves the service-level dimension needed to identify which service is busy.

Choose aggregation labels deliberately

Before aggregating, ask which question the result must answer:

  • Use sum by (job) for a service-level total.
  • Use sum by (instance) for a host or process view.
  • Keep status when success and error traffic must remain separate.
  • Drop high-cardinality dimensions such as an unbounded request ID unless the query specifically investigates them.

Aggregation changes cardinality. Removing a label can make a query manageable; retaining too many labels can return a series for every combination. Conversely, removing a label that explains the result can hide an important failure mode. Inspect the table output after each aggregation change.

Combine arithmetic, comparisons, and vector matching carefully

PromQL supports binary operators for arithmetic and comparisons, along with logical set-style operations. Arithmetic is useful for deriving rates and percentages, but combining vectors requires compatible label sets. Prometheus matches series according to their labels; an unmatched series may disappear, while an unintended many-to-many relationship can produce an unexpectedly large result or an invalid expression.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Compute an error percentage

This query calculates the share of requests whose status label begins with a five:

100 * sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))

The regular expression is fully anchored, so 5.. matches a three-character value beginning with 5, such as 500 or 503. The numerator and denominator are both aggregated to one compatible total before division. If you group one side by job, group the other side the same way so the division still has a clear one-to-one match.

Understand vector matching before joining series

When two instant vectors appear in one binary expression, Prometheus matches their label sets. A common failure is to aggregate one operand by job while leaving the other at job, instance, and status. The labels no longer line up, so the result can omit data or behave differently from the intended calculation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Make the dimensions explicit by aggregating both operands to the same labels, or use PromQL’s vector-matching modifiers only when you understand the cardinality they create. A many-to-one match can attach one reference series to several data series; careless matching can multiply work and obscure which labels came from which side.

Worked PromQL patterns

Question Expression What to verify
Are targets reporting now? up Check the returned job, instance, and value labels.
What is the current request counter by service? sum by (job) (http_requests_total) Confirm that the metric is a counter and that job is present.
What is the request rate by service? sum by (job) (rate(http_requests_total[5m])) Use a window containing several scrapes and inspect for missing series.
What percentage of requests are server errors? 100 * sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) Ensure the status label uses three-digit values and both totals cover the same traffic.
What is non-idle CPU usage by instance? 100 * (1 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))) Interpret the result as a percentage and confirm the idle counter exists for each instance.
What was a counter’s value one hour earlier? http_requests_total offset 1h offset shifts the evaluation time; it is not a second data source.

These are teaching patterns, not universal metric names. Substitute the names and labels emitted by your own exporters and applications. If a pattern returns nothing, first query the base metric without functions or aggregation.

Instant queries versus range queries

An instant query evaluates an expression at one timestamp. If no timestamp is supplied, the server uses the current evaluation time. In the UI, this is the Table view. It is best for a current status, a single value for an alert decision, or checking the labels produced by an expression.

A range query evaluates the same expression repeatedly at equally spaced steps between a start and end time. In the UI, this is the Graph view. The expression itself still needs to produce a scalar or instant vector at each step; the API wraps those point-in-time results into a time series.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Decision Instant query Range query
Evaluation One timestamp Many equally spaced timestamps
UI view Table Graph
Typical question “What is the value now?” “How did it change over this interval?”
API endpoint /api/v1/query /api/v1/query_range
Required time inputs Optional evaluation timestamp start, end, and step

A range query is not a replacement for a range selector. The selector, such as [5m], tells a function how much history to inspect at each evaluation step. The query range tells Prometheus which timestamps to evaluate across the larger display interval.

Query Prometheus through the HTTP API

External programs can fetch the result of a PromQL expression through Prometheus’s versioned HTTP API. The current stable API is under /api/v1.

Send an instant query

Use GET or POST on /api/v1/query. This shell example URL-encodes the expression:

curl -G 'http://localhost:9090/api/v1/query' 
  --data-urlencode 'query=up{job="prometheus"}'

The server returns JSON on success. The response includes a status and a result object whose data identifies the result type and the returned series or scalar. Write scripts against the documented response structure rather than scraping the web UI.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Send a range query

Use /api/v1/query_range and provide the expression, start time, end time, and step:

curl -G 'http://localhost:9090/api/v1/query_range' 
  --data-urlencode 'query=sum(rate(http_requests_total[5m]))' 
  --data-urlencode 'start=2026-01-01T11:00:00Z' 
  --data-urlencode 'end=2026-01-01T12:00:00Z' 
  --data-urlencode 'step=60s'

The timestamps here are illustrative RFC3339 values. The API also accepts Unix timestamps. Choose a step that provides the resolution you need without requesting an unnecessarily large number of evaluations. A long interval with a very small step can generate a large response and consume substantial server resources.

Handle API failures explicitly

Do not treat an HTTP response as successful merely because the request reached port 9090. Prometheus documents status codes including:

  • 400 for malformed parameters, such as an invalid time or missing required value.
  • 422 when the expression is syntactically valid but cannot be executed.
  • 503 when evaluation times out or is aborted.

Have automation check both the HTTP status and the JSON status field. Log the expression, time range, step, and server error message, while avoiding unbounded retries for a query that is inherently too expensive.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

A reliable workflow for writing and automating queries

  1. Identify the question and metric type. Decide whether you need a current value, a trend, a counter rate, a total over a window, or a derived percentage. Verify whether the source metric is a counter or a gauge.
  2. Test the bare metric name in Table view. Record the actual labels and confirm that samples exist at the time you are evaluating.
  3. Add exact matchers first. Narrow by stable labels such as job, environment, or service. Add negative or regular-expression matchers only when they express a real requirement.
  4. Add a range selector for history-dependent functions. Start with a window such as [5m], then adjust it to the scrape frequency and the stability you need.
  5. Apply the function before aggregation when appropriate. For counters, calculate rate() or increase() per series, then sum those results across the desired dimension.
  6. Aggregate to the labels needed by the question. Remove dimensions that create noise, but keep labels needed to identify a failing service, instance, or status.
  7. Inspect the table result again. Check the number of series, label sets, units, and unexpected missing values before graphing.
  8. Test historical behavior in Graph view. Look for restarts, gaps, resets, and changes in cardinality across the selected period.
  9. Move the verified expression to the API. Encode query parameters, use explicit RFC3339 or Unix times, set a sensible step, and handle 400, 422, and 503 responses.

Common problems and how to fix them

“No data” appears

  • Query the metric name without labels to check whether the name is correct.
  • Remove the newest matcher, then add matchers back one by one.
  • Check that the selected time includes a scrape and that the target is still reporting.
  • Confirm that the label exists on this metric; labels are not automatically shared by every metric from the same target.

The query returns far too many series

  • Add an exact job, service, environment, or instance matcher.
  • Use sum by (...) to retain only the operational dimension you need.
  • Avoid broad regular expressions over high-cardinality labels.
  • Inspect the table before sending the expression repeatedly from a dashboard or script.

A rate is empty or erratic

  • Confirm the metric is a counter and that the range window contains multiple samples.
  • Try a longer window if the scrape interval is long or samples are intermittent.
  • Check for target restarts and counter resets in the graph.
  • Do not apply counter functions to a gauge merely because the metric name ends in a familiar suffix.

An arithmetic expression drops series

  • Compare the labels on both operands in Table view.
  • Aggregate both sides to the same grouping labels before dividing or subtracting.
  • Review vector-matching rules when one side has one series per service and the other has one per instance.
  • Be especially cautious with many-to-one matches, which can increase the number of output series and the query cost.

The API request fails but the expression works in the UI

  • URL-encode braces, quotes, brackets, and spaces in the query parameter.
  • Use the correct endpoint: /api/v1/query for an instant evaluation and /api/v1/query_range for start/end/step evaluation.
  • Check that start and end are valid RFC3339 or Unix values and that step is supplied for a range query.
  • Inspect the JSON error and HTTP status rather than assuming a transport problem.

PromQL decisions that keep self-hosted monitoring usable

Self-hosted Prometheus makes it easy to add exporters and application labels, but every label combination can create another time series. Query design should therefore balance precision, meaning, and cost. A selector that is perfect for a one-time investigation may be unsuitable for a dashboard that refreshes every few seconds.

Use the smallest time window that answers the operational question while still providing enough samples for the function. Use a longer range only when smoothing or historical context is important. Keep aggregation close to the question: service owners generally need a service-level rate, while a host investigation may need the instance dimension. When comparing two expressions, make their label dimensions intentional before applying arithmetic.

Finally, treat the Table view as part of query development, not merely a display option. It exposes the actual result type, labels, values, and cardinality at one timestamp. Once those are correct, the Graph view can reveal behavior over time and the API can safely turn the expression into repeatable automation.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.