How to Fix 'BED File Not Sorted' Errors: A Troubleshooting Guide

By Dr. Zubair Khalid, DVM, MS, PhD ·

How to Fix 'BED File Not Sorted' Errors: A Troubleshooting Guide

The most likely cause of a "BED file not sorted" error is that the file was sorted lexicographically instead of numerically, so chr10 lands before chr2 and chr2:1000 lands before chr2:900. The first thing to check is whether your file passes a coordinate sort test. Run sort -C -t$'\t' -k1,1 -k2,2n yourfile.bed and read the exit status. A silent success means the file is coordinate sorted. A non-zero exit code means the file is not, and the rest of this guide tells you why and what to do about it.

Interval tools fail loudly on unsorted input because they stream through two files in lockstep. A single out-of-order record breaks the sweep, and the tool has no safe way to continue. The error is almost always a data preparation problem rather than a software bug, and it is almost always fixable in one or two commands once you know which of about ten causes you are dealing with.

What "Sorted" Means for a BED File

BED stands for Browser Extensible Data. It is a plain-text, tab-delimited format where each line describes one genomic interval. The minimum form has three columns: chromosome name, start, end. Coordinates are 0-based and half-open, meaning the start position is included in the interval and the end position is not. A BED line chr1 100 200 covers bases 101 through 200 in 1-based terms, or positions 100 through 199 in 0-based terms. This is defined by the UCSC Genome Browser specification, which is the reference standard for the format.

BED is one of the four dominant plain-text formats in genomics, alongside FASTA, FASTQ and VCF, and all four are large enough that the choice of compressor matters in production pipelines [1]. The four formats have different internal structure. FASTA is a header line followed by sequence. FASTQ carries the same sequence plus a per-base quality string, which is why a format-aware compressor has to treat the two differently to get good ratios [1]. BED carries no sequence at all, only coordinates and optional annotation columns.

Sorting a BED file means satisfying two separate conditions:

  1. Within each chromosome, the start coordinate never decreases from one line to the next.
  2. Chromosomes appear in some consistent order across the whole file, with each chromosome occupying one contiguous block.

Some tools check only condition 1. Others check both. And a third group, including bedtools intersect -sorted, checks that two or more files share the same chromosome order, which is a stricter requirement than either file being sorted on its own.

That third case is where most confusing failures come from. Each file is internally sorted and each would pass a solo check, but the pair fails because one file lists chr1, chr2, chr10 and the other lists chr1, chr10, chr2.

Why tools demand sorted input

The sorted requirement exists for performance. When two interval files are sorted in the same order, bedtools intersect can walk both files once, comparing only the intervals that currently overlap in coordinate space. That is a linear-time sweep. When the input is unsorted, the tool has to load one file into memory or build an index, which costs RAM and time on files that routinely reach millions of intervals.

The same logic applies across the ecosystem. A protocol for extracting targeted regions from a multi-sample VCF takes a reference genome, a BED file of target regions and a sample list, and pulls the targeted genotypes into a new VCF for population-genetic analysis [2]. A consensus variant-calling pipeline uses a BED file to apply coverage-aware voting so that low-coverage sites do not dominate the consensus call [3]. In both cases the BED file is a set of regions that the engine must traverse alongside another file. Sorted input is what makes that traversal cheap.

BED files are also frequently produced by generators rather than by hand. BED-Craft builds a BED file directly from a list of gene names for nanopore adaptive sampling, adding strand-aware buffer regions upstream and downstream of each target [4]. Any generator that writes records in the order of its input inherits that input's ordering, and a list of gene symbols is not a coordinate-sorted list. This is one of the quiet ways unsorted BED files enter a pipeline.

The Most Common Cause: Lexicographic Instead of Numeric Sort

Plain sort compares text. It does not know that the second column is a number. Given these four lines:

chr1	1000	2000
chr2	900	1000
chr2	1000	1100
chr10	500	900

sort file.bed produces chr1, then chr10, then chr2, then chr2. Inside chr2 it places 1000 before 900, because the character 1 sorts before 9. Both results violate the BED sort contract. chr10 before chr2 breaks chromosome blocking, and 1000 before 900 breaks the non-decreasing start rule.

The fix is to tell sort which field to compare and how:

sort -t$'\t' -k1,1 -k2,2n input.bed > output.bed

The flags break down as follows.

  • -t$'\t' sets the field separator to a literal tab. Without it, sort uses runs of blanks, which can merge fields on malformed lines.
  • -k1,1 sorts on field 1 only, up to field 1. This keeps chromosomes grouped.
  • -k2,2n sorts on field 2 as a number.

The n modifier is the critical part. Drop it and you are back to lexicographic ordering on the start column.

This command produces chr1, chr10, chr2 for the chromosome blocks. That satisfies condition 1 (each block is contiguous) and condition 2 (the order is consistent), so most tools accept it. It does not produce the canonical chr1, chr2, chr10 order that a reference genome dictionary expects. See the section on reference-order sorting below.

Locale effects

GNU sort respects the locale. In some non-C locales, character collation rules can change how punctuation and case are ordered in chromosome names such as chr1_KI270706v1_random. For reproducible output across machines, prefix the command:

LC_ALL=C sort -t$'\t' -k1,1 -k2,2n input.bed > output.bed

LC_ALL=C forces byte-order collation, which is what most genomics tools assume internally.

Troubleshooting Table

SymptomLikely causeHow to confirmFix
bedtools reports an out-of-order record that looks fineLexicographic sort, chr10 before chr2 or 1000 before 900sort -C -t$'\t' -k1,1 -k2,2n file.bed failsRe-sort with -k2,2n
Each file passes a solo sort check but -sorted still failsThe two files use different chromosome orders`cut -f1 a.bed \uniq and compare to cut -f1 b.bed \uniq`Sort both with the same -faidx or -g file
Error after editing the file in a spreadsheetCRLF line endings or space-separated columns`cat -A file.bed \head shows ^M` or spacestr -d '\r' < in.bed > out.bed, then re-sort
tabix refuses to indexFile is sorted but not bgzip-compressed, or not sorted at allfile file.bed.gz and `zcat file.bed.gz \head`sort then bgzip, then tabix -p bed
GATK or Picard rejects the interval listChromosome order differs from the sequence dictionaryCompare chromosome names against the .dict fileRe-sort using the dictionary order
Parser stops at line 1track, browser or # header lines`grep -n -E '^(track\browser\#)' file.bed`Remove headers for tools that do not accept them
bedtools merge complains about the same interval twiceDuplicate records, not a sort failure`sort file.bed \uniq -d \head`Deduplicate with bedtools merge after sorting
Interval has start greater than or equal to endMalformed coordinates, often from a 1-based source`awk -F'\t' '$2 >= $3' file.bed \head`Fix the offending records at the source
File is now empty after you "sorted" itShell redirection truncated the input before sort read itls -l file.bed shows 0 bytesRestore from backup, never redirect onto the input
Works locally, fails on the clusterDifferent sort build, locale or GNU versus BSD sortsort, version on both machinesPin LC_ALL=C and use bedtools sort

Causes in Order of Frequency

1. Lexicographic sort

Covered above. This is the single most common cause and it accounts for most reports of a "BED file not sorted" error. Spreadsheet applications sort text columns alphabetically by default, so a BED file exported from Excel or a similar tool is nearly always lexicographically sorted and nearly always wrong.

2. Chromosome naming mismatch

A file sorted on 1, 2, 3 and a file sorted on chr1, chr2, chr3 are each internally valid, but they do not share an order. bedtools intersect -sorted fails when it reaches the first record whose chromosome it cannot match in the expected order.

Confirm the naming scheme with:

cut -f1 file.bed | sort -u | head -30

If one file shows 1 and another shows chr1, you have found the problem. The fix is to strip or add the prefix consistently:

sed 's/^chr//' file.bed > file.noprefix.bed

Then re-sort both files by the same rule. Decide on one convention before you start the project, not after.

3. Header and comment lines

The BED format allows track and browser lines at the top for display purposes. Many command-line tools skip them. Some do not. A track line that reaches a parser as a data record has no numeric start column and can trigger a sort error or a parse error that reads like one.

Check with:

grep -n -E '^(track|browser|#)' file.bed

If headers exist and the downstream tool rejects them, remove them for that step:

grep -v -E '^(track|browser|#)' file.bed > clean.bed

Keep the original with headers for display in a genome browser, and use the cleaned version for computation.

4. Reference-order mismatch

This is the subtle one. A BED file sorted as chr1, chr10, chr2, ..., chr22, chr3, ... is internally consistent and passes sort -C. A GATK interval list, a Picard tool or a bedtools sort -faidx run all expect the order defined by the reference sequence dictionary or the .fai index, which is typically chr1, chr2, ..., chr10, ..., chr22, chrX, chrY, chrM.

Build the index if you have not:

samtools faidx reference.fa

The resulting reference.fa.fai has two useful columns: chromosome name and length. Sort against it with:

bedtools sort -i input.bed -faidx reference.fa.fai > sorted.bed

Or pass a genome file of two columns (name and size) with -g:

cut -f1,2 reference.fa.fai > genome.txt
bedtools sort -i input.bed -g genome.txt > sorted.bed

bedtools sort -faidx respects the order of the index file, which is the order samtools faidx wrote when it scanned the FASTA. That is the same order the rest of the reference-based toolchain expects.

5. Malformed coordinates

A line with start greater than or equal to end cannot be sorted meaningfully. Some parsers report it as a sort error because the interval engine cannot place it. Find them:

awk -F'\t' 'BEGIN{OFS="\t"} NF>=3 && ($2+0) >= ($3+0)' input.bed | head

Zero-length intervals (start equals end) are legal in some contexts and rejected in others. Check the tool's documentation before deleting them.

A related and very common error is treating a 1-based coordinate system as 0-based. VCF POS is 1-based. BED start is 0-based. Converting VCF to BED by copying POS into the start column shifts every interval by one base and, in some encodings, produces off-by-one errors that can look like ordering problems when adjacent intervals collide.

6. Space-separated instead of tab-separated

BED is defined as tab-delimited. Files that use runs of spaces instead of tabs sometimes parse and sometimes do not, depending on the tool. sort -t$'\t' then treats the whole line as one field and does not sort correctly.

Detect with:

cat -A input.bed | head -3

Tabs appear as ^I in that output. If you see plain spaces between columns, convert:

awk 'BEGIN{FS="[ ]+"; OFS="\t"} {print $1,$2,$3}' input.bed > fixed.bed

7. CRLF line endings

Files that passed through Windows produce lines ending in \r\n. The \r becomes part of the last field. In a three-column BED this usually breaks parsing rather than sorting, but in multi-column BED it can corrupt the end coordinate and produce records that compare incorrectly.

Detect with:

cat -A input.bed | head -1

A trailing ^M confirms it. Strip it:

tr -d '\r' < input.bed > fixed.bed

Then re-sort.

8. Compression and indexing

sort cannot read gzip in most implementations. Sorting a .bed.gz directly usually fails or produces garbage. Decompress, sort, then recompress with bgzip, which produces the block-gzip format that tabix requires:

zcat input.bed.gz | LC_ALL=C sort -t$'\t' -k1,1 -k2,2n | bgzip -c > sorted.bed.gz
tabix -p bed sorted.bed.gz

tabix builds a .tbi index and requires both bgzip compression and coordinate sorting in the same order used for the index. If the sort order and the index order disagree, queries return wrong or missing records without an error.

9. Concatenating sorted files

Two correctly sorted BED files concatenated do not make a correctly sorted BED file unless the second file's entire coordinate range comes after the first file's. If file A ends at chr5 and file B starts at chr1, the concatenation is out of order. Always re-sort after cat:

cat a.bed b.bed | LC_ALL=C sort -t$'\t' -k1,1 -k2,2n > merged.bed

10. Duplicates and overlaps

Duplicate records do not make a file unsorted, but they do make bedtools merge fail with a message that some users read as a sort error. Merge requires sorted input and typically expects to combine overlapping or adjacent intervals. Deduplicate first if exact duplicates are not meaningful:

LC_ALL=C sort -t$'\t' -k1,1 -k2,2n -u input.bed > dedup.bed

Note that -u on the full line removes exact duplicate lines only. Two intervals with the same coordinates but different names are not exact duplicates.

Diagnostic Sequence

Work through these steps in order. Each one is cheap and rules out a large class of causes.

  1. Check line count before and after every operation. wc -l input.bed output.bed. If the output is empty or shorter than expected, you truncated the input and the problem is not sorting at all.
  2. Check for headers. head -5 input.bed. Look for track, browser or #.
  3. Check column separation. cat -A input.bed | head -3. Look for ^I (tab) versus spaces and for trailing ^M.
  4. Check the chromosome order. cut -f1 input.bed | uniq | head -30. This shows the order of blocks as they appear in the file.
  5. Run the sort check. sort -C -t$'\t' -k1,1 -k2,2n input.bed. Exit code 0 means coordinate sorted, 1 means not.
  6. Run a natural-order check. sort -C -t$'\t' -k1,1V -k2,2n input.bed. This tests whether chromosomes are in chr1, chr2, chr10 order rather than chr1, chr10, chr2 order.
  7. Compare to the reference. Diff the chromosome block list against cut -f1 reference.fa.fai.
  8. Re-sort against the reference order if steps 5 and 6 pass but the downstream tool still fails. The tool is asking for dictionary order, not generic sort order.
  9. Re-run the failing command on a 1,000-line subset. If the subset works, the problem is in a specific region of the file and you can bisect to find the offending record.

Fixes and Exact Commands

Canonical coordinate sort

LC_ALL=C sort -t$'\t' -k1,1 -k2,2n input.bed > sorted.bed

Use this when the downstream tool only needs monotonic starts within contiguous chromosome blocks and does not care about the chromosome block order.

Natural chromosome order

LC_ALL=C sort -t$'\t' -k1,1V -k2,2n input.bed > sorted.bed

-V is a version-sort modifier that treats digit runs as numbers, giving chr1, chr2, chr10. It handles chr1 through chr22 correctly and handles chrX, chrY and chrM only by accident of alphabetical position. It does not reproduce reference dictionary order, which typically places chrX and chrY after chr22 and puts chrM last.

Reference dictionary order

samtools faidx reference.fa
bedtools sort -i input.bed -faidx reference.fa.fai > sorted.bed

This is the correct choice for GATK, Picard and any pipeline whose steps all share one reference.

Sorting multiple files identically

When two files must be compared, sort both with the same reference file:

bedtools sort -i a.bed -faidx reference.fa.fai > a.sorted.bed
bedtools sort -i b.bed -faidx reference.fa.fai > b.sorted.bed
bedtools intersect -a a.sorted.bed -b b.sorted.bed -sorted > overlap.bed

Sorting only one file is a frequent mistake. -sorted is a promise about both inputs.

Multi-column BED

sort -k1,1 -k2,2n preserves all columns because sort reorders whole lines. Nothing is lost. bedtools sort also preserves all columns. You do not need to cut down to three columns to sort and then paste columns back.

Sorting with awk when sort is unavailable

Rare, but useful in constrained environments:

awk -F'\t' '{print $1"\t"$2"\t"$0}' input.bed | LC_ALL=C sort -t$'\t' -k1,1 -k2,2n | cut -f3-

This duplicates the first two columns, sorts on the copies, then strips them. It is the same trick many legacy scripts use.

Safety note on redirection

Never write a sorted file back onto its own source. sort input.bed > input.bed truncates input.bed before sort reads it, and the result is an empty file. The same hazard applies to bedtools sort -i input.bed > input.bed and to zcat input.bed.gz | sort > input.bed.gz. Work on a copy, write to a new name, and only move the new file over the old one after checking wc -l.

Verifying the Fix

Sorting commands can succeed and still produce the wrong order. Verify before you move on.

  • sort -C -t$'\t' -k1,1 -k2,2n sorted.bed returns 0.
  • cut -f1 sorted.bed | uniq | head -30 shows each chromosome exactly once in a block.
  • cut -f1 sorted.bed | uniq | wc -l matches the number of distinct chromosomes in your reference.
  • awk -F'\t' 'prev==$1 && $2<p {print NR": "$0; exit} {p=$2; prev=$1}' sorted.bed prints nothing if starts are monotonic within each block.
  • bedtools intersect -a a.sorted.bed -b b.sorted.bed -sorted > /dev/null exits without an error, which is the practical test that matters.

For a BED file headed for tabix, confirm the compression type as well:

file sorted.bed.gz

The output should name BGZF or gzip in a way that indicates block-gzip. Plain gzip from gzip -c will not index correctly.

Prevention

Fix the order at the source

The most reliable prevention is to make every BED file in a project sorted against the same reference, using the same command, at the moment it is written. Add one sorting step immediately after every BED-producing step in your workflow. Sorting a few thousand intervals is nearly free. Debugging a failed multi-hour pipeline because of a header line is not.

Standardize chromosome names in the reference

Choose chr-prefixed or unprefixed names and enforce it. Most human references are distributed with chr prefixes, but some annotation sets are not. Converting once, early, and documenting the choice prevents a whole category of downstream comparison failures.

Avoid spreadsheets for BED files

Spreadsheet applications reorder columns, convert numeric-looking fields, strip leading zeros, change line endings and add quoting. Any one of these breaks a BED file. If a collaborator sends you a spreadsheet, export to tab-delimited text and run the diagnostic sequence before using it.

Keep a genome file in the project

Ship a genome.txt with two columns (chromosome, length) alongside the reference. Every sorting step can then use -g genome.txt and there is no ambiguity about order. Regenerate it whenever the reference changes.

Validate on ingest

Add a validation step at the start of every pipeline run:

set -euo pipefail
sort -C -t$'\t' -k1,1 -k2,2n "$BED" || { echo "BED is not coordinate sorted"; exit 1; }

Fail early with a clear message instead of failing deep inside an interval engine with a message about a single record.

Version your intermediates

Compressed intermediates save space and cost visibility. A .bed.gz that was sorted under one reference and reused under another is a silent source of wrong results. Store intermediates uncompressed during development and compress only at the archive step.

Common Mistakes and Limitations

Assuming bedtools sort matches the reference order. bedtools sort without -faidx or -g groups chromosomes in its own default order. That order is consistent, which satisfies most tools, but it is not necessarily the dictionary order that GATK expects. If a downstream step uses a .dict file, sort with the .fai from the same reference.

Sorting one file of a pair. -sorted in bedtools intersect is a claim about every input. Sorting -a and forgetting -b fails at the point where the two sweeps diverge.

Treating -V as universal. Version sort gets chr1, chr2, chr10 right but has no concept of chrX or chrM placement. Use -faidx when the reference matters.

Sorting after compression. sort on a .gz file either fails or sorts byte-order compressed data, which is meaningless. Decompress, sort, compress.

Confusing BED coordinates with VCF coordinates. BED is 0-based half-open. VCF is 1-based. A BED file built by copying VCF POS into column 2 is offset by one base, and a BED file built by copying POS into column 2 and using the same value for column 3 produces zero-length intervals.

Forgetting BED12 block structure. A BED12 record has a thick start, thick end, item RGB, block count, block sizes and block starts. A record can pass a simple chrom/start/end sort check while its internal block coordinates are inconsistent. Sorting does not repair internal block structure, and no sort command will detect a bad block list.

Expecting one canonical order. There is no single universal BED sort order. The format specification defines column meaning, not chromosome ordering. Order is a convention imposed by each tool and each reference. That flexibility is why the error appears in so many different wordings across tools.

Ignoring locale. A pipeline that works on your laptop and fails on a compute node is often a locale difference in sort. Pin LC_ALL=C.

Limitations. These fixes assume a well-formed BED file with numeric start and end columns. Files with embedded quoted fields, escaped tabs or non-ASCII chromosome names need manual inspection. The exact chromosome ordering required by a specific tool should always be confirmed against that tool's documentation and the reference sequence dictionary in use, because ordering conventions are tool-specific and can change between versions.

Frequently Asked Questions

Why does bedtools say my BED file is not sorted when it looks sorted to me?

Because the human eye reads chr10 as coming after chr2 and the software reads it as coming before. Plain text sorting puts chr10 before chr2 and puts 1000 before 900. Re-sort with a numeric key on column 2 and confirm with sort -C -t$'\t' -k1,1 -k2,2n file.bed.

Does sort -k1,1 -k2,2n fix every BED sorting error?

No. It fixes coordinate ordering within contiguous chromosome blocks, which resolves the majority of cases. It does not reproduce a reference dictionary order, does not remove header lines, does not fix CRLF endings or space-separated columns, and does not help when two files use different chromosome naming schemes.

Do I need to sort both files for bedtools intersect -sorted?

Yes. The -sorted flag asserts that both inputs are sorted in a compatible order. Sorting only the -a file is a common cause of a late failure. Sort both with the same genome file or the same .fai index.

How do I sort a BED file to match a reference genome order?

Create or reuse a .fai index with samtools faidx reference.fa, then run bedtools sort -i input.bed -faidx reference.fa.fai. Alternatively, pass a two-column genome file of chromosome names and lengths to bedtools sort -g genome.txt. Both reproduce the dictionary order used by GATK, Picard and similar tools.

Can I sort a gzipped BED file directly?

Not with standard sort, which reads plain text. Decompress, sort, and recompress with bgzip, which produces the block-gzip format that tabix can index. Plain gzip output will not produce a working .tbi index.

Why does my BED file fail after I edit it in a spreadsheet?

Spreadsheet applications frequently change line endings to CRLF, replace tabs with spaces, reinterpret numeric fields, and reorder columns. Check with cat -A file.bed | head and look for ^M and for spaces where tabs should be. Strip the carriage returns with tr -d '\r' and re-sort.

What is the difference between BED sort order and VCF sort order?

VCF requires records sorted by chromosome and by 1-based position, with contigs typically in reference dictionary order. BED requires 0-based half-open intervals sorted by chromosome and by 0-based start. The two formats use different coordinate origins, so a BED file derived from a VCF needs an explicit coordinate conversion as well as a sort.

Is a BED file 0-based or 1-based?

BED is 0-based and half-open according to the UCSC specification. The start coordinate is the first base of the interval and the end coordinate is one past the last base. This differs from GFF, VCF and most other genomics formats, which are 1-based, and the mismatch is a regular source of off-by-one errors when converting between formats.

Further Reading

Sources

  1. Byte-exact, format-aware compression for FASTA, FASTQ, BED and VCF files.
  2. A Protocol to Extract a Specific Genomic Region from a Public Whole-Genome Database and Modify Analytical Bin Length for Population Genetic Studies.
  3. ChoCallate: a computational pipeline for consensus germline variant calling in genomic sequencing data.
  4. BED-Craft for nanopore adaptive sampling: a tool for generating bed files with gene names as input data for enrichment sequencing.