Free Cloud Gaming Lag Test: 13 Steps, 40 Min [2026]

Free cloud gaming changed shape this week. On October 21, 2026, Xbox started letting Insiders test cloud gaming with ads and no Game Pass subscription, plus prepaid Cloud Playtime packs from $3.99 for 10 hours. NVIDIA’s pages still describe a free GeForce NOW entry tier, and Amazon’s listings say Prime members get a rotating Luna selection at no extra cost. That makes three ways to play for $0, and none of them fixes a bad connection.

The bad connection is where most free-tier experiments die. You sign up, sit in a queue, sit through an ad, and then the stream stutters because your Wi-Fi jitters, not because the service is weak. This tutorial builds a small Python tool called cloudlag that measures latency, jitter, packet loss and bufferbloat on your own line, then logs the queue and ad time of every free session. If you only want the account steps for the new test, our Xbox free-with-ads setup walkthrough covers them. Here we measure first and choose second.

You will finish with a 240-line, standard-library-only script, four commands that log to CSV files, and a clear answer on which free tier your network can actually carry. Plan on 13 steps and about 40 minutes. The tool is yours to keep, and re-running it takes under two minutes.

Google · Preferred Sources

Don't miss new tech stories on Google

Add Tech Insider once in the Google app and our stories appear in your news suggestions.

Add Now

What Is Actually Free in Cloud Gaming on October 23, 2026

Start with the facts, because the free options are moving fast and some of them are not yet public.

Xbox is the newest entrant and the least settled, and tech-ish covered the same test on October 23. Xbox’s own announcement and several outlets agree that the free ad-supported option is a test for Xbox Insiders without Game Pass, not a general launch. Availability, purchasing options, eligible games and features vary by market, device, account and preview ring. Xbox says a wider rollout of the paid packs is planned, but the details are not confirmed yet, and that is a stated plan, not a shipped product.

The paid side gives you a price yardstick for what “free” is worth. Packs are described as ad-free, with shorter waits and higher-quality streaming, and the hours stay available until you use them. Gamereactor’s report adds the other half of the story: monthly hour caps arrive for Game Pass cloud streaming in November.

Xbox route (Oct. 23, 2026)PriceCloud timeAdsStatus
Free cloud gaming test$0Not publishedYesInsider test only
Cloud Playtime, 10 hours$3.9910 hoursNoInsider preview
Cloud Playtime, 25 hours$7.9925 hoursNoInsider preview
Cloud Playtime, 50 hours$11.9950 hoursNoInsider preview
Game Pass EssentialPlan price applies5 hours per month from NovemberNoAnnounced limit
Game Pass Premium$14.99 per month10 hours per month from NovemberNoAnnounced limit
Highest Game Pass tier$22.99 per month15 hours per month from NovemberNoAnnounced limit

Do the division and the packs land at roughly $0.40, $0.32 and $0.24 per hour, from smallest to largest. PC Game Pass, listed at $13.99 per month, does not include cloud gaming according to Xbox’s listing.

On the other side of the market, NVIDIA’s pages describe GeForce NOW as an active service with a free entry tier next to paid memberships and Day Passes. The company’s blog shows Call of Duty: Modern Warfare 4 arriving on GeForce NOW at launch on October 22, the kind of night free-tier queues tend to grow. We cover that service’s free account flow in our GeForce NOW free tier with Steam guide. Amazon sits in the middle. A product listing dated October 16 says Prime members can play Fortnite and a rotating monthly selection through Luna with no additional cost, and Amazon Game Studios published a “One Year of Amazon Luna” post on October 23. Our Amazon Luna free tier setup shows the account side.

Notice what is missing from every one of those descriptions. No service publishes how long a free session lasts today, how long the queue runs, or how many ads you will watch. Those numbers change weekly, so this tutorial treats them as data you collect rather than facts you read.

Why Measure Before You Pick a Free Cloud Gaming Tier

Cloud gaming works by sending your button presses to a remote machine and streaming the video back. Every millisecond between you and that machine lands on top of the game’s own response time. A steady 30 ms feels fine. A connection that averages 30 ms but spikes to 150 ms every few seconds feels broken.

Four numbers describe that behavior. Median latency is the typical round trip. The 95th-percentile latency (p95) shows how bad the spikes get. Jitter measures how much consecutive samples differ from each other. Loss counts the probes that never came back. A fifth number, bufferbloat, is the extra latency that appears when something else on your network is using the line.

Vendors publish very little here. NVIDIA’s GeForce NOW system requirements page says it recommends a hardwired Ethernet connection or a router with a 5 GHz Wi-Fi connection, and then adds a hard rule: “We also require less than 80ms of network latency from an NVIDIA data center.” That is the only vendor-published latency number we could verify for the three services. In the sources we checked on October 23, Xbox publishes no minimum bandwidth, no maximum latency and no pass/fail benchmark for its cloud service, and Amazon’s pages gave none either.

So cloudlag works with one hard limit and four house rules. Treat the house rules as starting points and tighten them if you play competitive shooters.

MetricLimit in cloudlagOriginWhat it protects
Median round trip40 msHouse ruleEveryday responsiveness
p95 round trip80 msMatches NVIDIA’s published ceilingSpikes during fights
Jitter8 msHouse ruleEven frame pacing
Packet lossNot listedHouse ruleVisual artifacts, stalls
Bufferbloat30 ms added under loadHouse rulePlay while family streams

One limit deserves honesty up front. cloudlag times a TCP handshake to a well-connected front-door host, such as a CDN edge near you. That tells you whether your home network and ISP are healthy. It does not measure the path to a specific game data center. NVIDIA’s in-app network test remains the authority for GeForce NOW, and Step 12 shows where to use it.

Prerequisites and Versions

You need very little. The whole tool runs on the Python standard library, so there is nothing to install with pip and nothing to break when a package updates.

  • Python 3.10 or newer. We built and tested the code on Python 3.12.3.
  • Windows 11, macOS or a current Ubuntu release. Any terminal works.
  • A wired connection and a Wi-Fi connection on the same machine, so you can compare them.
  • About 40 minutes, with two of the later steps needing an evening session.
  • Optional accounts for the services you want to try: an Xbox account (plus Xbox Insider enrollment for the free test), an NVIDIA account, or an Amazon Prime membership.

The test machine also has to be able to run the services at all. NVIDIA’s requirements page lists a device floor for GeForce NOW, and these lines are copied from the page as we read it on October 23, 2026. Check the live page before you rely on them.

DeviceGeForce NOW minimum listed by NVIDIA
PC processorDual-core x86 or x64 CPU at 2.0 GHz or faster
MacAny Mac introduced in 2009 or later
Safari on macOSSafari 16.4 or later
ChromebookAt least 4GB RAM
Android phone or tablet1GB of available memory
iPhone or iPadOS version 15.4 or later

Old laptops and TVs often clear that floor but fail on Wi-Fi, which is why our free cloud gaming on a Chromebook, smart TV or old PC guide pairs well with the numbers you will collect here.

Build the Tool in Six Steps

You will write one file, in six pieces. Paste each block below the previous one, in order. The finished file has the same contents as the listing in the final section.

Step 1: Create the Project Folder and Check Python (2 min)

Make a folder, confirm your Python version, and create the empty file.

mkdir cloudlag
cd cloudlag
python3 --version
touch cloudlag.py

On Windows, use py --version and New-Item cloudlag.py in PowerShell. A healthy result looks like this.

Python 3.12.3

Anything from 3.10 up matches what we tested. Older releases may run the script, but we did not try them, so upgrade if you see 3.9 or lower.

Step 2: Write the Latency Probe Core (5 min)

The first piece resolves a hostname once, then times TCP connections to the resulting address. Resolving once matters. If you resolve on every probe, DNS lookups pollute the timings and you end up measuring your resolver instead of your line.

#!/usr/bin/env python3
"""cloudlag: check whether a connection is fit for free cloud gaming tiers."""
import argparse
import csv
import socket
import statistics
import threading
import time
import urllib.request
from datetime import datetime, timezone
from pathlib import Path

DATA_DIR = Path.home() / ".cloudlag"
PROBES_CSV = DATA_DIR / "probes.csv"
SESSIONS_CSV = DATA_DIR / "sessions.csv"

DEFAULT_HOSTS = ["www.nvidia.com", "www.xbox.com", "www.amazon.com", "1.1.1.1"]
LOAD_URL = "https://speed.cloudflare.com/__down?bytes=50000000"

# NVIDIA publishes one hard number: under 80 ms to its data center.
# Everything else below is our own house rule, so tune it.
GFN_MAX_MS = 80.0
LIMITS = {"median_ms": 40.0, "p95_ms": 80.0, "jitter_ms": 8.0,
          "loss_pct": 1.0, "bloat_ms": 30.0}


def resolve(host, port):
    return socket.getaddrinfo(host, port, type=socket.SOCK_STREAM)[0][4][0]


def tcp_rtt(ip, port, timeout):
    start = time.perf_counter()
    try:
        with socket.create_connection((ip, port), timeout=timeout):
            return (time.perf_counter() - start) * 1000.0
    except OSError:
        return None


def sample(ip, port, count, interval, timeout):
    results = []
    for _ in range(count):
        results.append(tcp_rtt(ip, port, timeout))
        time.sleep(interval)
    return results


def summarize(samples):
    ok = [s for s in samples if s is not None]
    stats = {"sent": len(samples),
             "loss_pct": 100.0 * (len(samples) - len(ok)) / max(len(samples), 1)}
    if not ok:
        return {**stats, "min_ms": None, "median_ms": None, "p95_ms": None,
                "max_ms": None, "jitter_ms": None}
    ordered = sorted(ok)
    p95 = ordered[min(len(ordered) - 1, round(0.95 * (len(ordered) - 1)))]
    diffs = [abs(a - b) for a, b in zip(ok, ok[1:])]
    return {**stats, "min_ms": ordered[0], "median_ms": statistics.median(ok),
            "p95_ms": p95, "max_ms": ordered[-1],
            "jitter_ms": statistics.mean(diffs) if diffs else 0.0}

A few choices deserve a sentence each. The tcp_rtt function uses a TCP handshake because it works the same on Windows, macOS and Linux without admin rights, whereas ICMP ping needs different parsing on every platform. The summarize function returns None values when every probe fails, so a dead host reports 100 percent loss instead of crashing. And the LIMITS dictionary sits at the top so you can tune it without hunting through functions.

Step 3: Add the Loaded-Latency Test (4 min)

A quiet line often looks perfect. The problem shows up when a download starts. This piece runs two background download streams while it probes, then reports how much the latency rose and how much throughput the line delivered.

def saturate(url, seconds, stop, counter):
    deadline = time.monotonic() + seconds
    while time.monotonic() < deadline and not stop.is_set():
        try:
            with urllib.request.urlopen(url, timeout=5) as resp:
                while time.monotonic() < deadline and not stop.is_set():
                    chunk = resp.read(65536)
                    if not chunk:
                        break
                    counter["bytes"] += len(chunk)
        except OSError:
            time.sleep(0.5)


def loaded_sample(ip, port, count, interval, timeout, url, streams=2):
    stop = threading.Event()
    counter = {"bytes": 0}
    seconds = count * interval + 2
    workers = [threading.Thread(target=saturate, args=(url, seconds, stop, counter),
                                daemon=True) for _ in range(streams)]
    started = time.monotonic()
    for w in workers:
        w.start()
    time.sleep(1.0)  # let the download ramp up before probing
    results = sample(ip, port, count, interval, timeout)
    stop.set()
    elapsed = max(time.monotonic() - started, 0.001)
    return results, counter["bytes"] * 8 / 1e6 / elapsed

The one-second sleep lets the downloads ramp up before the first probe. Without it, the first few samples look clean and pull the median down.

Step 4: Add the Grading Rules (3 min)

Next comes a function that compares a result to your limits and a tiny formatter. Keep it separate from the probe code, so you can grade old CSV rows later without re-running anything.

def verdict(row):
    checks = []
    for key, limit in LIMITS.items():
        value = row.get(key)
        if value is None:
            continue
        checks.append((key, value, limit, value <= limit))
    return checks


def fmt(value, digits=1):
    return "-" if value is None else f"{value:.{digits}f}"

Step 5: Add CSV Storage and the Probe Command (4 min)

This piece appends every run to ~/.cloudlag/probes.csv and wires the probe, the loaded test and the NVIDIA 80 ms check into one command.

def append_row(path, fields, row):
    DATA_DIR.mkdir(parents=True, exist_ok=True)
    is_new = not path.exists()
    with path.open("a", newline="") as fh:
        writer = csv.DictWriter(fh, fieldnames=fields)
        if is_new:
            writer.writeheader()
        writer.writerow(row)


PROBE_FIELDS = ["ts", "label", "host", "sent", "loss_pct", "min_ms", "median_ms",
                "p95_ms", "max_ms", "jitter_ms", "loaded_median_ms", "bloat_ms",
                "down_mbps"]


def cmd_probe(args):
    for host in args.host or DEFAULT_HOSTS:
        try:
            ip = resolve(host, args.port)
        except OSError as exc:
            print(f"{host}: DNS failed ({exc})")
            continue
        idle = summarize(sample(ip, args.port, args.count, args.interval, args.timeout))
        row = {"ts": datetime.now(timezone.utc).isoformat(timespec="seconds"),
               "label": args.label, "host": host, **idle,
               "loaded_median_ms": None, "bloat_ms": None, "down_mbps": None}
        if not args.no_load and idle["median_ms"] is not None:
            loaded, mbps = loaded_sample(ip, args.port, args.count, args.interval,
                                         args.timeout, args.load_url)
            busy = summarize(loaded)
            row["loaded_median_ms"] = busy["median_ms"]
            row["down_mbps"] = mbps
            if busy["median_ms"] is not None:
                row["bloat_ms"] = max(busy["median_ms"] - idle["median_ms"], 0.0)
        append_row(PROBES_CSV, PROBE_FIELDS, row)
        print(f"\n{host} [{args.label}]  ({ip})")
        for key, value, limit, ok in verdict(row):
            print(f"  {'PASS' if ok else 'FAIL'}  {key:<10} {value:8.1f}  (limit {limit})")
        if row["median_ms"] is not None:
            tag = "under" if row["median_ms"] < GFN_MAX_MS else "OVER"
            print(f"  NVIDIA 80 ms rule: median {row['median_ms']:.1f} ms is {tag}")
        if row["down_mbps"] is not None:
            print(f"  throughput during load: {row['down_mbps']:.0f} Mbps")

The CSV is the point of the whole exercise. One run proves little. Twenty runs across three connection types and four times of day prove a lot.

Step 6: Add the Report, Session Log and CLI (5 min)

The last piece reads the CSV back, groups runs by label and host, and adds a second log for free-tier sessions. First the report and the session commands.

def read_rows(path):
    if not path.exists():
        raise SystemExit(f"No data yet: run a probe first ({path})")
    with path.open(newline="") as fh:
        return list(csv.DictReader(fh))


def num(value):
    return float(value) if value not in ("", None) else None


def cmd_report(args):
    groups = {}
    for row in read_rows(PROBES_CSV):
        groups.setdefault((row["label"], row["host"]), []).append(row)
    print(f"{'label':<12}{'host':<18}{'runs':>5}{'median':>9}{'p95':>8}"
          f"{'jitter':>8}{'loss%':>7}{'bloat':>8}  verdict")
    for (label, host), rows in sorted(groups.items()):
        def avg(key):
            vals = [num(r[key]) for r in rows if num(r[key]) is not None]
            return statistics.mean(vals) if vals else None
        merged = {k: avg(k) for k in LIMITS}
        ok = all(passed for *_, passed in verdict(merged))
        print(f"{label:<12}{host:<18}{len(rows):>5}{fmt(merged['median_ms']):>9}"
              f"{fmt(merged['p95_ms']):>8}{fmt(merged['jitter_ms']):>8}"
              f"{fmt(merged['loss_pct']):>7}{fmt(merged['bloat_ms']):>8}  "
              f"{'READY' if ok else 'FIX FIRST'}")


SESSION_FIELDS = ["ts", "service", "queue_s", "ads_s", "play_min", "note"]


def cmd_log(args):
    append_row(SESSIONS_CSV, SESSION_FIELDS,
               {"ts": datetime.now(timezone.utc).isoformat(timespec="seconds"),
                "service": args.service, "queue_s": args.queue_s,
                "ads_s": args.ads_s, "play_min": args.play_min, "note": args.note})
    print("logged")


def cmd_sessions(args):
    groups = {}
    for row in read_rows(SESSIONS_CSV):
        groups.setdefault(row["service"], []).append(row)
    print(f"{'service':<22}{'n':>3}{'queue s':>9}{'ads s':>8}{'play min':>10}{'play share':>12}")
    for service, rows in sorted(groups.items()):
        queue = statistics.mean(float(r["queue_s"]) for r in rows)
        ads = statistics.mean(float(r["ads_s"]) for r in rows)
        play = statistics.mean(float(r["play_min"]) for r in rows)
        total = (queue + ads) / 60.0 + play
        print(f"{service:<22}{len(rows):>3}{queue:>9.0f}{ads:>8.0f}{play:>10.1f}"
              f"{100 * play / total:>11.1f}%")

Then the command-line entry point that ties everything together.

def main():
    parser = argparse.ArgumentParser(prog="cloudlag")
    sub = parser.add_subparsers(dest="cmd", required=True)

    p = sub.add_parser("probe", help="measure latency, jitter, loss and bufferbloat")
    p.add_argument("--host", action="append", help="repeat for several hosts")
    p.add_argument("--label", default="untagged", help="e.g. wired, wifi5, wifi6-evening")
    p.add_argument("--port", type=int, default=443)
    p.add_argument("--count", type=int, default=50)
    p.add_argument("--interval", type=float, default=0.2)
    p.add_argument("--timeout", type=float, default=1.0)
    p.add_argument("--load-url", default=LOAD_URL)
    p.add_argument("--no-load", action="store_true")
    p.set_defaults(func=cmd_probe)

    r = sub.add_parser("report", help="compare every saved run by label and host")
    r.set_defaults(func=cmd_report)

    s = sub.add_parser("log", help="record one free-tier session")
    s.add_argument("--service", required=True)
    s.add_argument("--queue-s", type=float, default=0)
    s.add_argument("--ads-s", type=float, default=0)
    s.add_argument("--play-min", type=float, required=True)
    s.add_argument("--note", default="")
    s.set_defaults(func=cmd_log)

    t = sub.add_parser("sessions", help="summarize queue, ad and play time")
    t.set_defaults(func=cmd_sessions)

    args = parser.parse_args()
    args.func(args)


if __name__ == "__main__":
    main()

Save the file. You now have cloudlag.py with four commands: probe, report, log and sessions.

Check That the Tool Works (Step 7, 2 min)

Before you trust any number, test the tool against a target whose answer you already know. Your own machine answers in a fraction of a millisecond. Open a second terminal in the same folder and start a throwaway web server.

python3 -m http.server 8765 --bind 127.0.0.1

Back in the first terminal, probe it without the load test.

python3 cloudlag.py probe --host 127.0.0.1 --port 8765 --label selftest --count 15 --interval 0.1 --no-load
python3 cloudlag.py report

We ran exactly this on Python 3.12.3 and got the following. Your decimals will differ slightly.

127.0.0.1 [selftest]  (127.0.0.1)
  PASS  median_ms       0.2  (limit 40.0)
  PASS  p95_ms          0.2  (limit 80.0)
  PASS  jitter_ms       0.0  (limit 8.0)
  PASS  loss_pct        0.0  (limit 1.0)
  NVIDIA 80 ms rule: median 0.2 ms is under
label       host               runs   median     p95  jitter  loss%   bloat  verdict
selftest    127.0.0.1             1      0.2     0.2     0.0    0.0       -  READY

Stop the throwaway server with Ctrl+C. Then delete the self-test row if you want a clean history, by removing the selftest line from ~/.cloudlag/probes.csv in any text editor.

Run the Measurements

Now the real work. The goal is a small table of results you can compare, so label every run honestly.

Step 8: Record the Wired Baseline (5 min)

Plug the machine into the router with an Ethernet cable and close everything that uses the network: cloud sync, game launchers, streaming tabs, phone backups. Then run the default probe, which tests four hosts.

python3 cloudlag.py probe --label wired-idle

Each host takes roughly 25 seconds, because the tool runs 50 idle probes and then 50 loaded probes. The defaults ping www.nvidia.com, www.xbox.com, www.amazon.com and 1.1.1.1. Those are the front doors of the three services plus a neutral public resolver, so you can tell a service-specific slowdown from a general one. Here is what a healthy wired result looks like. These are illustrative values, not a benchmark, and yours will differ.

www.nvidia.com [wired-idle]  (203.0.113.40)
  PASS  median_ms      14.2  (limit 40.0)
  PASS  p95_ms         17.9  (limit 80.0)
  PASS  jitter_ms       1.8  (limit 8.0)
  PASS  loss_pct        0.0  (limit 1.0)
  PASS  bloat_ms        9.4  (limit 30.0)
  NVIDIA 80 ms rule: median 14.2 ms is under
  throughput during load: 312 Mbps

The address in that sample comes from a documentation range, so do not look for it. Your output will show the real address.

Step 9: Add Wi-Fi, Evening and Router-Only Runs (6 min)

Unplug the cable, join your 5 GHz network, and stand where you would actually play. Run the same command with a different label.

python3 cloudlag.py probe --label wifi5-idle

Now isolate the Wi-Fi hop from the internet. Most routers answer on their web interface port, and that gives you a latency reading to the router alone. Replace the address with your router’s.

python3 cloudlag.py probe --host 192.168.1.1 --port 80 --label wifi5-router --no-load

If the router-only median is high or jittery, the problem lives in your living room, not at your ISP. If it is clean while the internet hosts are slow, the ISP or the route is the culprit. Finally, repeat the Wi-Fi run between 7 and 10 in the evening, when neighbors are streaming, and label it wifi5-evening. Phones and tablets deserve their own pass later, and our free cloud gaming on iPhone and Android guide covers that setup.

Step 10: Read the Report (3 min)

Run the report to put every label side by side.

python3 cloudlag.py report

An illustrative result after four runs looks like this.

label       host               runs   median     p95  jitter  loss%   bloat  verdict
wired-idle  www.nvidia.com        1    14.2    17.9     1.8    0.0     9.4  READY
wifi5-idle  www.nvidia.com        1    16.8    41.5     6.9    0.0    22.7  READY
wifi5-evening www.nvidia.com      1    19.3   112.0    14.6    2.0    58.3  FIX FIRST
wifi5-router 192.168.1.1          1     3.1    24.8     9.7    0.0       -  FIX FIRST

Read it from left to right. The evening Wi-Fi row fails on p95, jitter, loss and bloat at once, while the median looks fine. That pattern is the classic trap. A median-only speed test would have called this connection healthy. The router-only row shows a 3 ms median but nearly 10 ms of jitter, which points at the radio link rather than the ISP.

Fix What the Numbers Show (Step 11, 10 min)

Match your failing metric to the likely cause, fix one thing at a time, and re-run with a new label after each change. Changing three settings at once teaches you nothing.

Failing patternLikely causeFirst fix
High jitter on Wi-Fi, clean on wiredCrowded channel or weak signalMove to 5 GHz, change channel, move closer
High bloat only under loadRouter queue fills upEnable smart queue management (SQM) or QoS
Loss on every hostBad cable, flaky link or overloaded routerSwap the cable, reboot, test wired
High median to one service onlyRouting path to that CDNRetest later, try another DNS resolver
Clean idle, bad eveningNeighbor or household contentionSchedule play off-peak or add QoS
Router-only jitterWi-Fi radio problemWired connection or a closer access point

NVIDIA’s own advice lines up with the table. Its requirements page recommends “a hardwired Ethernet connection, or a router with a 5 GHz WiFi connection.” If you can run a cable, do that and skip the rest. Where you cannot, router-level SQM (fq_codel or CAKE on routers that support them) is the most effective fix for bufferbloat. Consumer routers ship it under names like Smart Queue, Adaptive QoS or Game Mode, so look for those labels.

After each change, run python3 cloudlag.py probe --label wifi5-after-qos and compare in the report. You want the bloat column to drop first. The median barely moves, and that is fine, because SQM exists to protect you under load.

Test Each Free Cloud Gaming Service From the Inside (Step 12, 8 min)

Your numbers tell you the home network is ready. Now check the services themselves, and log what each one costs in time.

For GeForce NOW, NVIDIA’s requirements page points to a knowledge base article on testing your network, and the client app includes a network test. Run it before your first session and compare its latency to your www.nvidia.com median. If the app reports under 80 ms from an NVIDIA data center, you meet the one rule NVIDIA states. If cloudlag looks great and the in-app result does not, the route to the data center is the issue, and no home fix will change it.

For Xbox, the free test needs Insider enrollment, and a missing option is the normal state for most accounts today. Enroll through the Xbox Insider Hub if you want in, and expect the test to show up only for eligible accounts and markets. Since Xbox publishes no latency target, your own session log is the best evidence you will get. Gamereactor’s report also reminds Game Pass subscribers that November caps their cloud time, so a log of real hours helps there too.

For Amazon Luna, you need Prime and a supported device. Amazon’s own pages gave no latency number we could verify, so lean on your measurements and the log below. Our Luna, GeForce NOW and Xbox Cloud Gaming head-to-head helps with the library side.

Now log each session right after it ends. Use seconds for queue and ad time, and minutes for play.

python3 cloudlag.py log --service "GeForce NOW free" --queue-s 240 --ads-s 0 --play-min 52 --note "evening, wifi5"
python3 cloudlag.py log --service "Xbox free test" --queue-s 30 --ads-s 45 --play-min 61 --note "wired"
python3 cloudlag.py log --service "Luna via Prime" --queue-s 15 --ads-s 0 --play-min 58
python3 cloudlag.py sessions

After a week of entries, the summary prints the share of your time that was actual play. Illustrative output:

service                 n  queue s   ads s  play min  play share
GeForce NOW free        4      255       0      49.5       92.1%
Luna via Prime          3       20       0      56.7       98.3%
Xbox free test          5       38      52      58.2       97.0%

Choose Your Free Cloud Gaming Tier (Step 13, 5 min)

Pair the two data sets. The probe results say whether your network qualifies. The session log says what each free tier charges you in waiting. Then use this decision grid.

What your data showsWhat to do
All labels READY, play share above 95 percentStay free. Any service works, so pick by library
READY on wired, FIX FIRST on Wi-FiPlay wired, or fix Wi-Fi before paying anyone
READY everywhere, long queues on one serviceMove to a service with shorter waits at your play time
READY everywhere, ads eat more than 10 percent of each sessionPrice a paid pack against your weekly hours
FIX FIRST everywhereDo not subscribe to anything yet

The paid-pack math from earlier is your reference point. If you watch ads for 45 seconds before a 60-minute session, you lose well under two percent to ads. If you play 8 to 10 hours a month, the 10-hour pack at $3.99 may cost less than the time you lose in queues, and the longer packs cost less per hour. Heavy players should compare against subscriptions in our GeForce NOW, Xbox Cloud Gaming and Boosteroid comparison, and anyone still choosing a service should skim our free cloud gaming setup roundup.

Remember the Xbox caveat. Cloud Playtime and the free test are Insider-stage features today, and Xbox described the paid rollout as planned for November. Reread your own log in a month before you commit to any plan built on them.

Common Pitfalls

Most bad conclusions from this kind of testing trace back to a handful of mistakes.

  1. Testing once, at 3 p.m. A single idle afternoon run hides evening congestion. Collect at least one run per time-of-day label before judging.
  2. Leaving background traffic on. A cloud backup or game update during the idle test inflates every metric. Close launchers, pause sync, and ask the household to stay off video for five minutes.
  3. Testing over a VPN. A VPN adds a hop and changes the route, so your numbers describe the VPN, not your line. Turn it off for the baseline.
  4. Trusting the median alone. The median hides spikes. Read p95, jitter and loss together, as the evening example in Step 10 shows.
  5. Running the loaded test on a metered link. Two streams can pull hundreds of megabytes in a minute on a fast line. Use --no-load on mobile hotspots and capped plans.
  6. Comparing hosts on different continents. A host far away will always look slower. Compare like with like, and keep the neutral resolver as your control.
  7. Treating the Insider test as general availability. Xbox’s free ad-supported option is a test, and November is a stated plan. Do not build a household budget on it.

For broader reading on where each service fits, start from our gaming coverage hub.

Troubleshooting

Nine problems cover almost every failure we have seen with a script like this.

  • “DNS failed” for every host. Your machine has no working resolver or no network. Open any website to confirm, then try --host 1.1.1.1, which skips DNS entirely.
  • 100 percent loss to one host, fine to others. That host may block or rate-limit port 443 from your network, or your firewall is interfering. Test --port 80 or pick another host.
  • Throughput during load reads 0 Mbps. The load URL is blocked, unreachable or intercepted by a proxy. Set --load-url to any large file you can reach, or skip the load test with --no-load.
  • Bufferbloat always shows as 0. Either your router already runs SQM, which is good news, or the download never ramped up. Check that the throughput line shows a believable speed.
  • Results change wildly between runs. Raise --count to 100 and keep the machine idle. Short runs cannot capture p95 reliably.
  • “No data yet” from report or sessions. You have not run a probe or log command on this machine, or you changed the home directory. The files live in ~/.cloudlag/.
  • “python3 is not recognized” on Windows. Use py instead, or install Python from the official site and tick the option that adds it to PATH.
  • The Xbox free option never appears. That is expected for non-Insider accounts and unsupported markets. Confirm enrollment in the Xbox Insider Hub and check again after the November rollout Xbox has described.
  • Latency is excellent but the stream still stutters. The bottleneck may be the service, the route to the data center, or your device’s decoder. Run the in-app test and try a wired connection or a different client.
  • The CSV looks wrong in a spreadsheet. Some locales expect semicolons as separators. Use the import wizard and set comma as the delimiter.

Advanced Tips

Once the basics work, a few additions turn a one-off check into a monitor.

Schedule it. Add a cron job on macOS or Linux, or a Task Scheduler entry on Windows, that runs python3 cloudlag.py probe --label scheduled --no-load --count 30 every hour. After two weeks you will have a map of exactly when your ISP degrades, which beats guessing why Thursday nights are bad.

Lengthen the sample before big decisions. Fifty probes give a rough p95. For a purchase or a cancellation, run --count 200 --interval 0.25 once at peak time.

Test the home-streaming path too. If you stream from your own gaming PC, point --host at that machine’s address and a port it listens on to see the LAN or VPN path. Our self-hosted Sunshine, Moonlight and Tailscale setup pairs naturally with this, because Tailscale can add its own latency.

Compare IPv4 and IPv6. The tool uses whichever address the resolver returns first. Pass an IPv6 literal with --host to test that path separately, since some networks route the two very differently.

Plot it. Import probes.csv into any spreadsheet, filter by label, and chart p95 and jitter over time. Spikes you cannot see in the report appear immediately on a line chart.

Raise the bar for shooters. Tighten LIMITS to a 25 ms median and 5 ms jitter if you play fast-paced titles. These are your preferences, not vendor requirements.

The Complete Project

The finished tool is one file plus two CSV logs that the script creates itself.

cloudlag/
  cloudlag.py            # the six pieces from Steps 2 to 6, in order
~/.cloudlag/
  probes.csv             # one row per host per probe run
  sessions.csv           # one row per free-tier session

If you saved the pieces separately, join them in the order they appear in Steps 2 through 6, then compile-check the result.

python3 -m py_compile cloudlag.py && echo OK

Keep this cheat sheet next to the monitor.

python3 cloudlag.py probe --label wired-idle
python3 cloudlag.py probe --label wifi5-evening
python3 cloudlag.py probe --host 192.168.1.1 --port 80 --label wifi5-router --no-load
python3 cloudlag.py report
python3 cloudlag.py log --service "GeForce NOW free" --queue-s 240 --play-min 52
python3 cloudlag.py sessions

Here is the whole workflow in one sentence: measure wired, measure Wi-Fi, fix the worst metric, log real sessions for a week, then let the play-share column decide whether free is good enough.

FAQ

Is Xbox Cloud Gaming really free now? Only partly. As of October 23, 2026, Xbox is testing a free, ad-supported option for Xbox Insiders without Game Pass. It is not a general release, and availability varies by market, device and account.

What latency do I need for free cloud gaming? NVIDIA states that it requires under 80 ms of network latency from an NVIDIA data center for GeForce NOW. Xbox and Amazon published no equivalent number in the sources we checked, so cloudlag uses stricter house rules for everyday comfort.

Does cloudlag measure the exact path to a game server? No. It times connections to well-connected front-door hosts, which tells you whether your home network and ISP are healthy. Use each service’s in-app network test for the final check.

How much does Xbox Cloud Playtime cost? The introductory Insider prices are $3.99 for 10 hours, $7.99 for 25 hours and $11.99 for 50 hours. Xbox says a broader rollout is planned for November 2026.

Will the load test use a lot of data? It can. Two download streams for roughly 12 seconds can move hundreds of megabytes on a fast line. Add --no-load on metered connections.

Is Wi-Fi good enough for cloud gaming? It can be, if the numbers say so. NVIDIA recommends wired Ethernet or a 5 GHz Wi-Fi router, and your own Wi-Fi labels in the report show whether your signal behaves.

Do I need to know Python to use this? No. Paste the six pieces into one file and run the commands. The script uses only the standard library, so there is nothing else to install.

Marcus Chen

Marcus Chen

Gaming & Consumer Tech Editor

Marcus Chen is a senior editor at Tech Insider, where he leads coverage of the US online gaming market, including sweepstakes and social casinos, alongside consumer technology. He evaluates operators on their published terms, licensing and RNG certifications, stated redemption policies, and corroborating independent reporting, and writes plainly about what the evidence supports. Tech Insider does not run first-party money tests and does not gamble with reader funds. Marcus has reported on the technology and online-gaming industries for more than a decade.

View all articles