How to Triage a Bad FastQC Report
By the end of this guide you will have a reproducible quality-control pass over your own FASTQ files and a written decision for each failing module: ignore it, trim it, or flag the library for re-sequencing. You need the raw FASTQ files from your provider (gzipped is fine), roughly 2x the FASTQ size in free disk, and a machine with 8 or more cores. Install fastqc (0.12.1 or newer), fastp, seqkit, multiqc, and optionally kraken2 with a standard database if you suspect contamination. Everything below assumes paired-end Illumina data, since that is what whole-genome and RNA-seq deliveries almost always are. The central idea is that FastQC reports properties of a file, not verdicts about an experiment, and several of its red crosses are expected for perfectly usable libraries.
1. Run FastQC so the output is interpretable
Start by running FastQC on every file separately and keeping the raw data directories as well as the HTML. The HTML alone is not enough.
mkdir -p qc/fastqc
fastqc -t 8 --extract -o qc/fastqc -d /tmp \
raw/sample_R1.fastq.gz raw/sample_R2.fastq.gz
Setting -t 8 runs eight files in parallel and allocates about 250 MB of heap per thread, so do not set it higher than your file count or your RAM. The --extract flag writes fastqc_data.txt alongside the HTML, which is the file you will grep in later steps. The -d /tmp setting matters when your temp directory is small, because FastQC spills there while sorting duplicate counts.
One flag changes interpretation more than any other. FastQC groups base positions into bins after position 10, so a 150 bp read is summarized in bins of 5 or 10 bases and a single bad cycle is smeared across neighbors. Re-run with --nogroup when you need per-cycle resolution to identify exactly where quality collapses.
fastqc -t 8 --nogroup --extract -o qc/fastqc_nogroup raw/sample_R1.fastq.gz
Then aggregate. Even for one sample, MultiQC makes the numbers easier to compare across lanes and read directions than clicking through HTML.
multiqc -o qc/multiqc qc/fastqc
2. Read Basic Statistics before you look at any plot
Basic Statistics is the only module that never fails, and it is the one that tells you whether the rest of the report is worth reading. Pull the header out of the extracted data directly.
sed -n '/>>Basic Statistics/,/>>END_MODULE/p' \
qc/fastqc/sample_R1_fastqc/fastqc_data.txt
Check four fields. Total Sequences should be close to what you paid for: a 30x human genome needs roughly 300 Gbp of passing sequence, which is about one billion 150 bp read pairs. The high-coverage 1000 Genomes release was built at 30x mean coverage on NovaSeq using 150 bp paired-end reads, and that remains a reasonable yardstick for a personal whole genome 1. Sequence length should be a single number or a narrow range. A wide range on data you believed was untrimmed means someone already ran a trimmer. %GC should be about 41 for human whole-genome DNA and 45 to 52 for human polyA RNA-seq. A %GC of 60 on a sample that should be human is a contamination signal, and no amount of trimming will fix it.
If Total Sequences is far below expectation, stop here and go back to your provider. Quality plots on an under-loaded flow cell are not useful evidence about the library.
3. Separate real quality decay from the module’s thresholds
Per Base Sequence Quality fails when the lower quartile at any position drops below 5 or the median drops below 20. It warns at lower quartile 10 or median 25. Those cutoffs are fixed and position-agnostic, which is why long reads on a four-color instrument fail routinely in the final 20 cycles while remaining perfectly alignable.
The question to ask is not whether the box plot turns red but how much sequence is below your usable threshold. Compute that instead of eyeballing it.
seqkit fx2tab -q -n -H raw/sample_R1.fastq.gz | head
fastp -i raw/sample_R1.fastq.gz -I raw/sample_R2.fastq.gz \
--json qc/fastp_pre.json --html qc/fastp_pre.html \
-o /dev/null -O /dev/null
jq '.summary.before_filtering | {q20_rate, q30_rate, read1_mean_length}' qc/fastp_pre.json
We treat a q30_rate above 0.85 for DNA as normal, 0.75 to 0.85 as usable with soft-clipping aligners, and below 0.70 as a run problem worth raising with the provider. Per Sequence Quality Scores is the better companion plot here, because it fails only when the most common per-read mean quality is under 20. A secondary bump of low-quality reads in that distribution usually corresponds to one edge of the flow cell rather than a library defect.
Per Tile Sequence Quality resolves that directly. It plots deviation from the mean quality per tile per cycle, and a horizontal blue stripe means one tile misbehaved for the whole run while a vertical stripe means a transient event such as a bubble. A failure limited to a few tiles is a sequencing artifact, and the affected reads will either fail alignment or be down-weighted by base quality during variant calling.
4. Deal with adapters, poly-G, and overrepresented sequences together
These three modules usually fail as a group, and they have a single explanation: your insert was shorter than your read length, so the sequencer read past the fragment.
Adapter Content warns at 5% and fails at 10% adapter presence at any position. FastQC only screens for the contaminants in Configuration/adapter_list.txt. That list covers Illumina universal (AGATCGGAAGAGC), Nextera transposase (CTGTCTCTTATACACATCT), and SOLID small RNA. Read-through of 20% at the 3’ end of a 150 bp read means a median insert near 130 bp and is a library-prep finding. It is not a base-calling problem.
Overrepresented Sequences is the module people misread most. It reports on the first 100,000 reads only, and it truncates sequences longer than 75 bp to 50 bp when counting. For RNA-seq, a top hit that is a highly expressed transcript is the expected result, and for a polyA library it is often a mitochondrial or ribosomal fragment 2. Blast the sequence before treating it as a defect.
sed -n '/>>Overrepresented sequences/,/>>END_MODULE/p' \
qc/fastqc/sample_R1_fastqc/fastqc_data.txt | head -20
Two-color chemistry, meaning NextSeq and NovaSeq, calls “no signal” as a G base. Short inserts therefore produce long runs of high-quality G at the read end, which show up as a poly-G spike in Overrepresented Sequences and an upward drift of G in Per Base Sequence Content. These reads are worse than low-quality reads because their bases carry high Phred scores and will align somewhere. Trim them explicitly.
5. Interpret sequence content and GC distribution against the assay
Per Base Sequence Content fails when any base deviates from its complement by more than 20% at any position. The classic benign failure is the first 10 to 13 bases of RNA-seq data, where random hexamer priming produces a reproducible nucleotide bias. That bias is a property of the protocol and does not indicate a bad run 2. The same module also fails legitimately for amplicon or targeted panels, where the first cycles are a fixed primer.
Per Sequence GC Content is the most informative module for contamination. FastQC fits a normal distribution to your observed GC and fails when deviations exceed 30% of reads. A single shifted peak suggests you sequenced something other than what you think. A clearly bimodal curve means two genomes in one tube, which matters because contaminant reads inflate false-positive variant calls in exome and genome analysis alike 3. Contamination screening is now standard practice in fields where a single mislabeled sample changes the conclusion, as in bacterial genomics for tuberculosis surveillance 4.
When GC looks bimodal, identify the second organism before doing anything else.
kraken2 --db /refs/k2_standard --threads 16 --paired \
--report qc/kraken_report.txt \
raw/sample_R1.fastq.gz raw/sample_R2.fastq.gz > /dev/null
sort -k1,1nr qc/kraken_report.txt | head -15
6. Treat the duplication modules as estimates, because they are not measurements
Sequence Duplication Levels warns above 20% non-unique reads and fails above 50%. It builds that estimate from the first 100,000 distinct sequences, truncated to 50 bp, so it both undercounts optical duplicates and overcounts genuine high-coverage regions. For RNA-seq it fails almost by construction, because a highly expressed gene produces the same fragment many times over 2.
For DNA, the number you should trust comes after alignment. FastQC does not give you that number.
bwa-mem2 mem -t 16 -R '@RG\tID:1\tSM:sample\tPL:ILLUMINA' \
/refs/GRCh38.fa raw/sample_R1.fastq.gz raw/sample_R2.fastq.gz \
| samtools sort -@ 4 -o aln/sample.bam
samtools index aln/sample.bam
picard MarkDuplicates I=aln/sample.bam O=aln/sample.md.bam \
M=qc/dup_metrics.txt
grep -A2 LIBRARY qc/dup_metrics.txt
A PERCENT_DUPLICATION above roughly 0.20 on a whole genome points to too few input molecules and too many PCR cycles. That is a library-construction limit, and re-sequencing the same library will not add information.
7. Trim once, with the minimum necessary, then re-run FastQC
Our default is a single fastp pass. It handles adapters, poly-G, and quality in one read of the data. It also emits a JSON file you can diff against the pre-trim run.
fastp \
-i raw/sample_R1.fastq.gz -I raw/sample_R2.fastq.gz \
-o trim/sample_R1.fastq.gz -O trim/sample_R2.fastq.gz \
--detect_adapter_for_pe \
--trim_poly_g --poly_g_min_len 10 \
--cut_right --cut_right_window_size 4 --cut_right_mean_quality 20 \
--length_required 36 \
--thread 8 \
--json qc/fastp_post.json --html qc/fastp_post.html
--detect_adapter_for_pe infers the adapter from read overlap rather than trusting a hardcoded sequence, which is what you want when the provider did not tell you the kit. --cut_right is a sliding window from the 5’ end that stops at the first window below mean Q20 and discards the rest, which is gentler than a hard --trim_tail and much gentler than global quality filtering. --length_required 36 drops fragments too short to align uniquely in a human genome.
Two things we deliberately do not do. We do not quality-trim aggressively before variant calling, because modern aligners soft-clip and callers already use base qualities, and heavy trimming biases indel representation. We also do not trim RNA-seq reads for quality at all when using a splice-aware aligner with soft-clipping, beyond adapter and poly-G removal. Trimming changes read length distributions and can shift transcript-level quantification.
Then re-run FastQC on the trimmed files and compare in MultiQC.
fastqc -t 8 --extract -o qc/fastqc_post trim/sample_R*.fastq.gz
multiqc -o qc/multiqc_final qc/fastqc qc/fastqc_post qc/fastp_post.json
8. Validate against alignment metrics, which are the real test
FastQC cannot tell you whether your data answers your question. Alignment can. A library with three failing FastQC modules that aligns at 98% with even coverage is fine, and a clean report that aligns at 60% is not.
samtools flagstat aln/sample.md.bam > qc/flagstat.txt
mosdepth -n --fast-mode -t 4 qc/sample aln/sample.md.bam
python -c "
import gzip
tot=n=0
for l in gzip.open('qc/sample.mosdepth.global.dist.txt.gz','rt'):
pass
"
awk '$1==\"total\"' qc/sample.mosdepth.global.dist.txt
For human whole-genome data, look for above 95% of reads mapped and above 90% properly paired. You also want a mean coverage within 15% of your target and above 90% of the genome at 10x or more. Compare your callable fraction against published cohorts rather than intuition, since 30x short-read data has known coverage gaps in segmental duplications and other repeats 1. If you plan to call variants, run a contamination estimator such as VerifyBamID2 as well, because cross-sample contamination above a few percent inflates heterozygous calls in a way no trimming step removes 3.
None of these numbers say anything about your health. They say whether the measurement is sound. Any interpretation of a specific variant or biomarker belongs with a clinician or a clinical genetics service, working from a validated report and not from a FASTQ file.
Common problems
Quality collapses after cycle 100 in read 2 but read 1 is clean. This is normal asymmetry, since read 2 is sequenced later after more template damage. Soft-clipping aligners handle it. Only act if q30_rate for read 2 falls below about 0.70.
Per Base Sequence Content fails across the first 12 bases of RNA-seq. Expected from random priming, and not a reason to trim 2. Trimming those bases costs you information at transcript ends.
Per Tile Sequence Quality is a wall of red. Check whether the FASTQ headers still contain tile identifiers. If your provider re-headered or merged files, the module reads garbage coordinates and the plot is meaningless.
Duplication fails at 60% on RNA-seq. Almost always expression structure rather than a PCR artifact. Confirm by checking whether duplicates concentrate in the highest-expressed genes after alignment, and avoid deduplicating RNA-seq without unique molecular identifiers.
GC content is bimodal with a peak near 50%. Run kraken2 and check for bacterial or adapter-dimer content. Adapter dimers appear as a very sharp overrepresented sequence at the exact adapter, and they should be removed at the sequencing facility rather than in software.
FastQC crashes or hangs on a large file. Usually a heap or temp-space problem. Raise the heap by editing the wrapper or setting _JAVA_OPTIONS=-Xmx2g, point -d at a large scratch disk, and lower -t so that threads times 250 MB fits in RAM.
Everything passes and the alignment rate is still low. Your reference is wrong, your sample is not what the label says, or the library captured an unintended region. Take the top overrepresented sequences and BLAST them. Species-level surprises are common in pipelines that hardcode a reference genome 4.
Footnotes
-
Marta Byrska-Bishop, Uday S. Evani, Xuefang Zhao, et al. High-coverage whole-genome sequencing of the expanded 1000 Genomes Project cohort including 602 trios. Cell, 2022. https://doi.org/10.1016/j.cell.2022.08.004 ↩ ↩2
-
Aishwarya Gondane, Harri M. Itkonen. Revealing the History and Mystery of RNA-Seq. Current Issues in Molecular Biology, 2023. https://doi.org/10.3390/cimb45030120 ↩ ↩2 ↩3 ↩4
-
Rajasree Odumpatta, Arumugam Mohanapriya. Next generation sequencing exome data analysis aids in the discovery of SNP and INDEL patterns in Parkinson’s disease. Genomics, 2020. https://doi.org/10.1016/j.ygeno.2020.04.025 ↩ ↩2
-
Robyn S. Lee, Marcel A. Behr. The implications of whole-genome sequencing in the control of tuberculosis. Therapeutic Advances in Infectious Disease, 2015. https://doi.org/10.1177/2049936115624630 ↩ ↩2
Pothos builds longitudinal molecular profiles of individuals: whole-genome sequencing, single-cell RNA sequencing, and plasma proteomics, delivered as a detailed report, every data file, and stored cells. Build your profile.